From 9409fc97b425e963e9d4b566153f4c776eee5810 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 21 Jun 2021 18:54:29 +0300 Subject: [PATCH 01/11] ergofund WIP1 --- build.sbt | 13 +- .../org/ergoplatform/scanner/Scanner.scala | 192 +++++++++++++++++- 2 files changed, 189 insertions(+), 16 deletions(-) diff --git a/build.sbt b/build.sbt index 04395e5..5d30f13 100644 --- a/build.sbt +++ b/build.sbt @@ -4,6 +4,12 @@ version := "0.1" scalaVersion := "2.12.7" +resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", + "Bintray" at "https://jcenter.bintray.com/", //for org.ethereum % leveldbjni-all + "SonaType" at "https://oss.sonatype.org/content/groups/public", + "Typesafe maven releases" at "https://dl.bintray.com/typesafe/maven-releases/", + "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/") + libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.3.0" val circeVersion = "0.12.3" @@ -16,8 +22,5 @@ libraryDependencies ++= Seq( libraryDependencies += "org.ergoplatform" %% "ergo" % "v4.0.13-5251a78b-SNAPSHOT" -resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", - "Bintray" at "https://jcenter.bintray.com/", //for org.ethereum % leveldbjni-all - "SonaType" at "https://oss.sonatype.org/content/groups/public", - "Typesafe maven releases" at "https://dl.bintray.com/typesafe/maven-releases/", - "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/") +// swaydb just for an experiment +libraryDependencies += "io.swaydb" %% "swaydb" % "0.16.2" \ No newline at end of file diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 6619a92..e227b28 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -1,22 +1,36 @@ package org.ergoplatform.scanner +import com.google.common.primitives.UnsignedInts import scalaj.http.{Http, HttpOptions} import io.circe.parser._ -import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} +import org.bouncycastle.util.BigIntegers +import org.ergoplatform.ErgoBox.{R4, R5, R6, R7, R8} +import org.ergoplatform.{DataInput, ErgoAddressEncoder, ErgoBox, ErgoBoxCandidate, ErgoScriptPredef, P2PKAddress, UnsignedInput} import org.ergoplatform.modifiers.history.BlockTransactions -import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} +import org.ergoplatform.nodeView.state.ErgoStateContext import org.ergoplatform.nodeView.wallet.scanning.{ContainsAssetPredicate, EqualsScanningPredicate, ScanningPredicate} +import org.ergoplatform.settings.{ErgoSettings, LaunchParameters} +import org.ergoplatform.wallet.boxes.ErgoBoxSerializer +import org.ergoplatform.wallet.interpreter.{ErgoProvingInterpreter, TransactionHintsBag} +import org.ergoplatform.wallet.secrets.{ExtendedSecretKey, PrimitiveSecretKey} import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import scorex.util.encode.Base16 import sigmastate.Values +import sigmastate.Values.{ByteArrayConstant, IntConstant, LongConstant, SigmaPropConstant, SigmaPropValue} +import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.eval.RuntimeIRContext +import sigmastate.interpreter.ContextExtension +import special.sigma.SigmaProp +import swaydb.Glass import scala.annotation.tailrec import scala.collection.mutable +import scala.util.Try -object Scanner extends App with ScorexLogging { - +object BasicDataStructures { type ScanId = Int type Identifier = String @@ -24,30 +38,79 @@ object Scanner extends App with ScorexLogging { case class ExtractionRules(scans: Seq[Scan]) - case class ExtractedOutput(output: ErgoBox, tx: ErgoTransaction) + case class ExtractedOutput(scan: Scan, output: ErgoBox, tx: ErgoTransaction) case class ExtractedInput(spentBox: ErgoBox, spendingTx: ErgoTransaction) case class ExtractionResult(spentTrackedInputs: Seq[ExtractedInput], createdOutputs: Seq[ExtractedOutput]) +} + +object DatabaseStructures { + + import swaydb._ + import swaydb.serializers.Default._ + import scala.concurrent.duration._ + + //Create an in-memory map instance + val pledgeBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + + val campaignBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + + val paymentBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + + val systemBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + + val nftIndex = memory.Map[String, String, Nothing, Glass]() + +} + +object Scanner extends App with ScorexLogging { + + import BasicDataStructures._ + import DatabaseStructures._ + + def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) + + val controlBoxNftIdString = "43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700" + val controlBoxNftId = Base16.decode(controlBoxNftIdString).get + val controlBoxScanId = 1 + val controlBoxScan = Scan(controlBoxScanId, ContainsAssetPredicate(Digest32 @@ controlBoxNftId)) + + val tokenSaleNftId = Base16.decode("28faaad9cc3090ee03686589b08c3965be271c86b650a88f356e57da568862fc").get + val tokenSaleScanId = 2 + val tokenSaleScan = Scan(tokenSaleScanId, ContainsAssetPredicate(Digest32 @@ tokenSaleNftId)) + // ErgoFund pledge script, we find pledges by finding boxes (outputs) protected by the script val pledgeScriptBytes = ErgoAddressEncoder(0: Byte) .fromString("XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16") .get .contentBytes - val pledgeScan = Scan(1, EqualsScanningPredicate(ErgoBox.R1, Values.ByteArrayConstant(pledgeScriptBytes))) + val pledgeScanId = 3 + val pledgeScan = Scan(pledgeScanId, EqualsScanningPredicate(ErgoBox.R1, Values.ByteArrayConstant(pledgeScriptBytes))) // We scan for ErgoFund campaign data, stored in outputs with the ErgoFund token val campaignTokenId = Base16.decode("08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c").get - val campaignScan = Scan(2, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) + val campaignScanId = 4 + val campaignScan = Scan(campaignScanId, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) + + // + val paymentTree = ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + .fromString("9f2UU1Jo52WDMCCCu9GYdiWSmb5V7jzP8FdRkVqqvCHyD72gTTR") + .get + .script + val paymentPkBytes = paymentTree.bytes - val exampleRules = ExtractionRules(Seq(pledgeScan, campaignScan)) + val paymentScanId = 5 + val paymentScan = Scan(paymentScanId, EqualsScanningPredicate(ErgoBox.R1, Values.ByteArrayConstant(paymentPkBytes))) + + val exampleRules = ExtractionRules(Seq(controlBoxScan, tokenSaleScan, pledgeScan, campaignScan, paymentScan)) val serverUrl = "http://213.239.193.208:9053/" - val bestChainHeaderIds = mutable.Map[Int, Identifier](504910 -> "c89d70cf4d7664676dd53e091617d40aa183ba6e0485cd077b7b74e983c77fd2") + val bestChainHeaderIds = mutable.Map[Int, Identifier](509323 -> "9744efa8817753229972967dd9feeb6dafd1a625f5ad3fe38fe998ff6f122219") private def getJsonAsString(url: String): String = { Http(s"$url") @@ -86,7 +149,7 @@ object Scanner extends App with ScorexLogging { tx.outputs.foreach { out => extractionRules.scans.foreach { scan => if (scan.scanningPredicate.filter(out)) { - createdOutputs += ExtractedOutput(out, tx) + createdOutputs += ExtractedOutput(scan, out, tx) } } } @@ -96,7 +159,102 @@ object Scanner extends App with ScorexLogging { ExtractionResult(Seq.empty, createdOutputs) } - var lastHeight = 504910 // we start from some recent block + def ergoFundProcess(extractionResult: BasicDataStructures.ExtractionResult): Unit = { + extractionResult.createdOutputs.foreach { out => + val boxId = bytesToString(out.output.id) + out.scan.id match { + case i: Int if i == controlBoxScanId => + nftIndex.put(bytesToString(controlBoxNftId), boxId) + systemBoxes.put(boxId, out.output.bytes) + log.info("Registered control box: " + boxId) + case i: Int if i == tokenSaleScanId => + nftIndex.put(bytesToString(tokenSaleNftId), boxId) + systemBoxes.put(boxId, out.output.bytes) + log.info("Registered tokensale box: " + boxId) + case i: Int if i == pledgeScanId => + + case i: Int if i == campaignScanId => + + case i: Int if i == paymentScanId => + log.info("Registered payment box: " + boxId) + paymentBoxes.put(boxId, out.output.bytes) + } + } + } + + def registerCampaign(currentHeight: Int, + campaignId: Int, + campaignDesc: String, + campaignScript: SigmaPropConstant, + deadline: Int, + minToRaise: Long): Try[Unit] = Try { + val fee = 10000000 //0.01 ERG + + implicit val me = ErgoAddressEncoder.apply(ErgoAddressEncoder.MainnetNetworkPrefix) + val keyBytes = Base16.decode("128f6307c0fb8094e733633d2ee4c3bbd06228411aa86cc5566e9d543b41623b").get + val key = DLogProverInput(BigIntegers.fromUnsignedByteArray(keyBytes)) + + val controlBoxId = nftIndex.get(controlBoxNftIdString).get + val controlBoxBytes = systemBoxes.get(controlBoxId).get + val controlBox = ErgoBoxSerializer.parseBytes(controlBoxBytes) + + val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] + log.info("price: " + price) + + val inputBoxes = mutable.Buffer[ErgoBox]() + var collectedAmount = 0L + val inputsCount = paymentBoxes.values.takeWhile { boxBytes => + val box = ErgoBoxSerializer.parseBytes(boxBytes) + inputBoxes += box + collectedAmount += box.value + collectedAmount < price + fee + }.count + + log.info("inputs: " + inputsCount) + log.info("Collected payments: " + collectedAmount) + log.info("price: " + price) + log.info("price + fee: " + (price + fee)) + + if (price + fee > collectedAmount) { + throw new Exception("price + fee > collectedAmount") + } + + val inputs = inputBoxes.map(b => new UnsignedInput(b.id, ContextExtension.empty)).toIndexedSeq + val dataInputs = IndexedSeq(DataInput(controlBox.id)) + + val regs = Map( + R4 -> IntConstant(campaignId), + R5 -> ByteArrayConstant(campaignDesc.getBytes("UTF-8")), + R6 -> campaignScript, + R7 -> IntConstant(deadline), + R8 -> LongConstant(minToRaise) + ) + + val campaignAddress = me.fromString("4MQyMKvMbnCJG3aJ").get.script + val campaignPrice = 1000000000L + val campaignOutput = new ErgoBoxCandidate(campaignPrice, campaignAddress, currentHeight, + additionalRegisters = regs) + + val feeAmount = 2000000L // 0.002 ERG + val feeOutput = new ErgoBoxCandidate(feeAmount, ErgoScriptPredef.feeProposition(), currentHeight) + + val changeAmount = collectedAmount - campaignPrice - feeAmount + val changeOutput = new ErgoBoxCandidate(changeAmount, paymentTree, currentHeight) + + val outputs = IndexedSeq[ErgoBoxCandidate](campaignOutput, changeOutput, feeOutput) + val unsignedTx = UnsignedErgoTransaction(inputs, dataInputs, outputs) + + implicit val ir = new RuntimeIRContext + val settings = ErgoSettings.read() + + val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(key)), LaunchParameters) + val tx = prover.sign(unsignedTx, inputBoxes.toIndexedSeq, IndexedSeq(controlBox), + ErgoStateContext.empty(settings), TransactionHintsBag.empty) + println("tx: " + tx) + } + + + var lastHeight = 509323 // we start from some recent block @tailrec def step(): Unit = { @@ -110,6 +268,7 @@ object Scanner extends App with ScorexLogging { lastHeight = newHeight bestChainHeaderIds += newHeight -> headerId val extractionResult = processTransactions(headerId, exampleRules) + ergoFundProcess(extractionResult) val extractedCount = extractionResult.createdOutputs.length log.info("Extracted: " + extractedCount + " outputs") if (extractedCount > 0) { @@ -118,6 +277,17 @@ object Scanner extends App with ScorexLogging { step() case None => log.info(s"No block found @ height $newHeight") + val campaignId = 0 + val campaignDesc = "Test campaign" + val campaignScript = SigmaPropConstant( + ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + .fromString("9f2UU1Jo52WDMCCCu9GYdiWSmb5V7jzP8FdRkVqqvCHyD72gTTR") + .get.asInstanceOf[P2PKAddress].pubkey + ) + + val deadline = 550000 + val minToRaise = 100000000000L + registerCampaign(lastHeight, campaignId, campaignDesc, campaignScript, deadline, minToRaise) Thread.sleep(60 * 1000) // 1 minute step() } From 0dac48a38d1aff6fef35b67a70cae9eef56d8123 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 13 Jul 2021 17:41:55 +0300 Subject: [PATCH 02/11] campaign box issuance working --- .../org/ergoplatform/scanner/Scanner.scala | 97 +++++++++++++------ 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index e227b28..138066d 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -1,10 +1,10 @@ package org.ergoplatform.scanner -import com.google.common.primitives.UnsignedInts +import io.circe.Json import scalaj.http.{Http, HttpOptions} import io.circe.parser._ import org.bouncycastle.util.BigIntegers -import org.ergoplatform.ErgoBox.{R4, R5, R6, R7, R8} +import org.ergoplatform.ErgoBox.{R4, R5, R6, R7, R8, TokenId} import org.ergoplatform.{DataInput, ErgoAddressEncoder, ErgoBox, ErgoBoxCandidate, ErgoScriptPredef, P2PKAddress, UnsignedInput} import org.ergoplatform.modifiers.history.BlockTransactions import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} @@ -13,22 +13,22 @@ import org.ergoplatform.nodeView.wallet.scanning.{ContainsAssetPredicate, Equals import org.ergoplatform.settings.{ErgoSettings, LaunchParameters} import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.wallet.interpreter.{ErgoProvingInterpreter, TransactionHintsBag} -import org.ergoplatform.wallet.secrets.{ExtendedSecretKey, PrimitiveSecretKey} +import org.ergoplatform.wallet.secrets.PrimitiveSecretKey import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import scorex.util.encode.Base16 -import sigmastate.Values -import sigmastate.Values.{ByteArrayConstant, IntConstant, LongConstant, SigmaPropConstant, SigmaPropValue} +import sigmastate.{SByte, Values} +import sigmastate.Values.{ByteArrayConstant, CollectionConstant, IntConstant, LongConstant, SigmaPropConstant} import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval.RuntimeIRContext import sigmastate.interpreter.ContextExtension -import special.sigma.SigmaProp -import swaydb.Glass +import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} +import special.collection.Coll +import sigmastate.eval.Colls import scala.annotation.tailrec import scala.collection.mutable -import scala.util.Try - +import sigmastate.eval._ object BasicDataStructures { type ScanId = Int @@ -72,12 +72,13 @@ object Scanner extends App with ScorexLogging { def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) - val controlBoxNftIdString = "43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700" + val controlBoxNftIdString = "baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74cf99a" val controlBoxNftId = Base16.decode(controlBoxNftIdString).get val controlBoxScanId = 1 val controlBoxScan = Scan(controlBoxScanId, ContainsAssetPredicate(Digest32 @@ controlBoxNftId)) - val tokenSaleNftId = Base16.decode("28faaad9cc3090ee03686589b08c3965be271c86b650a88f356e57da568862fc").get + val tokenSaleNftIdString = "48ab32adaf7eb132c76c077fdeb3df6a6692417168f1541c83a5545c10c63f81" + val tokenSaleNftId = Base16.decode(tokenSaleNftIdString).get val tokenSaleScanId = 2 val tokenSaleScan = Scan(tokenSaleScanId, ContainsAssetPredicate(Digest32 @@ tokenSaleNftId)) @@ -91,7 +92,7 @@ object Scanner extends App with ScorexLogging { val pledgeScan = Scan(pledgeScanId, EqualsScanningPredicate(ErgoBox.R1, Values.ByteArrayConstant(pledgeScriptBytes))) // We scan for ErgoFund campaign data, stored in outputs with the ErgoFund token - val campaignTokenId = Base16.decode("08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c").get + val campaignTokenId = Base16.decode("07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce").get val campaignScanId = 4 val campaignScan = Scan(campaignScanId, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) @@ -110,14 +111,25 @@ object Scanner extends App with ScorexLogging { val serverUrl = "http://213.239.193.208:9053/" - val bestChainHeaderIds = mutable.Map[Int, Identifier](509323 -> "9744efa8817753229972967dd9feeb6dafd1a625f5ad3fe38fe998ff6f122219") + val bestChainHeaderIds = mutable.Map[Int, Identifier](516000 -> "1a9cad7745fb39ba30b6d1a43f524eb43870ddf31bd04f6ac7a3b95f1a23f8a0") private def getJsonAsString(url: String): String = { Http(s"$url") .header("Content-Type", "application/json") .header("Accept", "application/json") .header("Charset", "UTF-8") - .option(HttpOptions.readTimeout(10000)) + .option(HttpOptions.readTimeout(120000)) + .asString + .body + } + + private def postJson(url: String, json: Json): String = { + Http(s"$url") + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Charset", "UTF-8") + .postData(json.toString()) + .option(HttpOptions.readTimeout(120000)) .asString .body } @@ -172,8 +184,12 @@ object Scanner extends App with ScorexLogging { systemBoxes.put(boxId, out.output.bytes) log.info("Registered tokensale box: " + boxId) case i: Int if i == pledgeScanId => + log.info("Registered pledge box: " + boxId) case i: Int if i == campaignScanId => + val value = out.output.value + + log.info("Registered campaign box: " + boxId) case i: Int if i == paymentScanId => log.info("Registered payment box: " + boxId) @@ -187,27 +203,44 @@ object Scanner extends App with ScorexLogging { campaignDesc: String, campaignScript: SigmaPropConstant, deadline: Int, - minToRaise: Long): Try[Unit] = Try { + minToRaise: Long): Unit = { val fee = 10000000 //0.01 ERG + def deductToken(tokens: Coll[(TokenId, Long)], index: Int): Coll[(TokenId, Long)] = { + val atIdx = tokens.apply(index) + tokens.updated(index, atIdx._1 -> (atIdx._2 - 1)) + } + implicit val me = ErgoAddressEncoder.apply(ErgoAddressEncoder.MainnetNetworkPrefix) - val keyBytes = Base16.decode("128f6307c0fb8094e733633d2ee4c3bbd06228411aa86cc5566e9d543b41623b").get + val keyBytes = Base16.decode("").get val key = DLogProverInput(BigIntegers.fromUnsignedByteArray(keyBytes)) val controlBoxId = nftIndex.get(controlBoxNftIdString).get val controlBoxBytes = systemBoxes.get(controlBoxId).get val controlBox = ErgoBoxSerializer.parseBytes(controlBoxBytes) + log.info("Control box ID: " + controlBoxId) + + val tokensaleBoxId = nftIndex.get(tokenSaleNftIdString).get + val tokensaleBoxBytes = systemBoxes.get(tokensaleBoxId).get + val tokensaleBox = ErgoBoxSerializer.parseBytes(tokensaleBoxBytes) + val tokansaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, + deductToken(tokensaleBox.additionalTokens, 1), tokensaleBox.additionalRegisters) + val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] - log.info("price: " + price) + val devRewardScriptBytes = controlBox.additionalRegisters(R5).asInstanceOf[CollectionConstant[SByte.type]].value.asInstanceOf[Coll[Byte]].toArray + val devRewardScript = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(devRewardScriptBytes) + val devRewardOutput = new ErgoBoxCandidate(price, devRewardScript, currentHeight) val inputBoxes = mutable.Buffer[ErgoBox]() - var collectedAmount = 0L + inputBoxes += tokensaleBox + var collectedAmount = tokensaleBox.value + val inputsCount = paymentBoxes.values.takeWhile { boxBytes => val box = ErgoBoxSerializer.parseBytes(boxBytes) inputBoxes += box collectedAmount += box.value - collectedAmount < price + fee + collectedAmount < tokensaleBox.value + price + fee }.count log.info("inputs: " + inputsCount) @@ -231,17 +264,22 @@ object Scanner extends App with ScorexLogging { ) val campaignAddress = me.fromString("4MQyMKvMbnCJG3aJ").get.script - val campaignPrice = 1000000000L - val campaignOutput = new ErgoBoxCandidate(campaignPrice, campaignAddress, currentHeight, - additionalRegisters = regs) + val campaignAmt = 1000000000L + val campaignTokens: Array[(TokenId, Long)] = Array((Digest32 @@ campaignTokenId) -> 1) + val campaignOutput = new ErgoBoxCandidate( + campaignAmt, + campaignAddress, + currentHeight, + additionalTokens = Colls.fromArray(campaignTokens), + additionalRegisters = regs) val feeAmount = 2000000L // 0.002 ERG val feeOutput = new ErgoBoxCandidate(feeAmount, ErgoScriptPredef.feeProposition(), currentHeight) - val changeAmount = collectedAmount - campaignPrice - feeAmount + val changeAmount = collectedAmount - campaignAmt - feeAmount - tokensaleBox.value - price val changeOutput = new ErgoBoxCandidate(changeAmount, paymentTree, currentHeight) - val outputs = IndexedSeq[ErgoBoxCandidate](campaignOutput, changeOutput, feeOutput) + val outputs = IndexedSeq[ErgoBoxCandidate](tokansaleOutput, devRewardOutput, campaignOutput, changeOutput, feeOutput) val unsignedTx = UnsignedErgoTransaction(inputs, dataInputs, outputs) implicit val ir = new RuntimeIRContext @@ -249,12 +287,14 @@ object Scanner extends App with ScorexLogging { val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(key)), LaunchParameters) val tx = prover.sign(unsignedTx, inputBoxes.toIndexedSeq, IndexedSeq(controlBox), - ErgoStateContext.empty(settings), TransactionHintsBag.empty) - println("tx: " + tx) + ErgoStateContext.empty(settings), TransactionHintsBag.empty).get + val json = ErgoTransaction.ergoLikeTransactionEncoder(tx) + val txId = postJson(serverUrl + "transactions", json) + println("tx id: " + txId) } - var lastHeight = 509323 // we start from some recent block + var lastHeight = 516000 // we start from some recent block @tailrec def step(): Unit = { @@ -287,7 +327,7 @@ object Scanner extends App with ScorexLogging { val deadline = 550000 val minToRaise = 100000000000L - registerCampaign(lastHeight, campaignId, campaignDesc, campaignScript, deadline, minToRaise) + // registerCampaign(lastHeight, campaignId, campaignDesc, campaignScript, deadline, minToRaise) Thread.sleep(60 * 1000) // 1 minute step() } @@ -298,4 +338,5 @@ object Scanner extends App with ScorexLogging { //start scanning cycle from lastHeight step() + } From 0235d80c42658f71a0a6a9b5724a5048e21709c4 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Wed, 21 Jul 2021 14:49:27 +0300 Subject: [PATCH 03/11] campaigns serialization and db --- .../org/ergoplatform/scanner/Scanner.scala | 92 +++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 138066d..24c651c 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -10,6 +10,7 @@ import org.ergoplatform.modifiers.history.BlockTransactions import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.nodeView.state.ErgoStateContext import org.ergoplatform.nodeView.wallet.scanning.{ContainsAssetPredicate, EqualsScanningPredicate, ScanningPredicate} +import org.ergoplatform.scanner.ErgoFundStructures.Campaign import org.ergoplatform.settings.{ErgoSettings, LaunchParameters} import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.wallet.interpreter.{ErgoProvingInterpreter, TransactionHintsBag} @@ -18,17 +19,20 @@ import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import scorex.util.encode.Base16 import sigmastate.{SByte, Values} -import sigmastate.Values.{ByteArrayConstant, CollectionConstant, IntConstant, LongConstant, SigmaPropConstant} +import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ErgoTree, IntConstant, LongConstant, SigmaPropConstant} import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval.RuntimeIRContext import sigmastate.interpreter.ContextExtension -import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} +import sigmastate.serialization.ErgoTreeSerializer import special.collection.Coll import sigmastate.eval.Colls import scala.annotation.tailrec import scala.collection.mutable import sigmastate.eval._ +import special.sigma.SigmaProp +import swaydb.data.slice.Slice +import swaydb.serializers.Serializer object BasicDataStructures { type ScanId = Int @@ -46,13 +50,59 @@ object BasicDataStructures { } +object ErgoFundStructures { + + type CampaignId = Int + + case class Campaign(id: Int, desc: String, script: ErgoTree, deadline: Int, toRaise: Long) + + case class Pledge() + + //binary campaign data serializer for SwayDB + implicit val campaignSerialiser = + new Serializer[Campaign] { + override def write(cmp: Campaign): Slice[Byte] = { + val descLen = cmp.desc.length + val scriptBytes = cmp.script.bytes + val scriptBytesLen = scriptBytes.length + + Slice + .ofBytesScala(200) //allocate enough length to add all fields + .addInt(cmp.id) + .addInt(descLen) + .addStringUTF8(cmp.desc) + .addInt(scriptBytesLen) + .addAll(scriptBytes) + .addInt(cmp.deadline) + .addLong(cmp.toRaise) + .close() //optionally close to discard unused space + } + + override def read(slice: Slice[Byte]): Campaign = { + val reader = slice.createReader() + val id = reader.readInt() + val descLen = reader.readInt() + val desc = reader.readStringUTF8(descLen) + val scriptBytesLen = reader.readInt() + val scriptBytes = reader.read(scriptBytesLen).toArray + val deadline = reader.readInt() + val toRaise = reader.readLong() + + val script = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(scriptBytes) + Campaign(id, desc, script, deadline, toRaise) + } + } + +} + object DatabaseStructures { import swaydb._ import swaydb.serializers.Default._ import scala.concurrent.duration._ + import org.ergoplatform.scanner.ErgoFundStructures._ - //Create an in-memory map instance + // Create an in-memory map instance val pledgeBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() val campaignBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() @@ -63,12 +113,15 @@ object DatabaseStructures { val nftIndex = memory.Map[String, String, Nothing, Glass]() + // Campaigns + val campaigns = memory.Map[CampaignId, Campaign, Nothing, Glass]() } object Scanner extends App with ScorexLogging { import BasicDataStructures._ import DatabaseStructures._ + import ErgoTree.fromProposition def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) @@ -179,21 +232,46 @@ object Scanner extends App with ScorexLogging { nftIndex.put(bytesToString(controlBoxNftId), boxId) systemBoxes.put(boxId, out.output.bytes) log.info("Registered control box: " + boxId) + case i: Int if i == tokenSaleScanId => nftIndex.put(bytesToString(tokenSaleNftId), boxId) systemBoxes.put(boxId, out.output.bytes) log.info("Registered tokensale box: " + boxId) + case i: Int if i == pledgeScanId => + val pledgeBox = out.output + val value = pledgeBox.value + val campaignId = pledgeBox.get(R4).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + + val backerScriptProp = pledgeBox.get(R5).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] + val projectScriptProp = pledgeBox.get(R6).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] + val backerScriptTree = ErgoTree.fromProposition(backerScriptProp) + val projectScriptTree = ErgoTree.fromProposition(projectScriptProp) + + val deadline = pledgeBox.get(R7).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + val minToRaise = pledgeBox.get(R8).get.asInstanceOf[LongConstant].value.asInstanceOf[Long] + + // todo: pledge structure + log.info("Registered pledge box: " + boxId) case i: Int if i == campaignScanId => - val value = out.output.value - - log.info("Registered campaign box: " + boxId) + val campaignBox = out.output + val value = campaignBox.value // todo: check the valur is above threshold + val campaignId = campaignBox.get(R4).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + val campaignDescBytes = campaignBox.get(R5).get.asInstanceOf[CollectionConstant[SByte.type]].value.asInstanceOf[Array[Byte]] + val campaignDesc = new String(campaignDescBytes, "UTF-8") + val campaignScriptProp = campaignBox.get(R6).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] + val campaignScriptTree = ErgoTree.fromProposition(campaignScriptProp) + val campaignDeadline = campaignBox.get(R7).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + val campaignToRaise = campaignBox.get(R8).get.asInstanceOf[LongConstant].value.asInstanceOf[Long] + + val campaign = Campaign(campaignId, campaignDesc, campaignScriptTree, campaignDeadline, campaignToRaise) + log.info(s"Registered campaign box: $boxId, campaign: $campaign") case i: Int if i == paymentScanId => - log.info("Registered payment box: " + boxId) paymentBoxes.put(boxId, out.output.bytes) + log.info("Registered payment box: " + boxId) } } } From 9c8ae4d03aa1ff187873df62be7b7ed45829fe66 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 22 Jul 2021 01:18:25 +0300 Subject: [PATCH 04/11] pledge serialization --- .../org/ergoplatform/scanner/Scanner.scala | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 24c651c..e25c554 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -10,8 +10,8 @@ import org.ergoplatform.modifiers.history.BlockTransactions import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.nodeView.state.ErgoStateContext import org.ergoplatform.nodeView.wallet.scanning.{ContainsAssetPredicate, EqualsScanningPredicate, ScanningPredicate} -import org.ergoplatform.scanner.ErgoFundStructures.Campaign -import org.ergoplatform.settings.{ErgoSettings, LaunchParameters} +import org.ergoplatform.scanner.ErgoFundStructures.{Campaign, Pledge} +import org.ergoplatform.settings.{Constants, ErgoSettings, LaunchParameters} import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.wallet.interpreter.{ErgoProvingInterpreter, TransactionHintsBag} import org.ergoplatform.wallet.secrets.PrimitiveSecretKey @@ -54,9 +54,12 @@ object ErgoFundStructures { type CampaignId = Int - case class Campaign(id: Int, desc: String, script: ErgoTree, deadline: Int, toRaise: Long) + type PledgeId = String - case class Pledge() + case class Campaign(id: CampaignId, desc: String, script: ErgoTree, deadline: Int, toRaise: Long) + + case class Pledge(pledgeId: PledgeId, campaignId: CampaignId, backerScript: ErgoTree, projectScript: ErgoTree, + deadline: Int, toRaise: Long) //binary campaign data serializer for SwayDB implicit val campaignSerialiser = @@ -93,6 +96,47 @@ object ErgoFundStructures { } } + //binary campaign data serializer for SwayDB + implicit val pledgeSerialiser = + new Serializer[Pledge] { + override def write(pl: Pledge): Slice[Byte] = { + val backerScriptBytes = pl.backerScript.bytes + val backerScriptBytesLen = backerScriptBytes.length + + val projectScriptBytes = pl.projectScript.bytes + val projectScriptBytesLen = projectScriptBytes.length + + Slice + .ofBytesScala(200) //allocate enough length to add all fields + .addStringUTF8(pl.pledgeId) + .addInt(pl.campaignId) + .addInt(backerScriptBytesLen) + .addAll(backerScriptBytes) + .addInt(projectScriptBytesLen) + .addAll(projectScriptBytes) + .addInt(pl.deadline) + .addLong(pl.toRaise) + .close() //optionally close to discard unused space + } + + override def read(slice: Slice[Byte]): Pledge = { + val reader = slice.createReader() + + val pledgeId = reader.readStringUTF8(Constants.ModifierIdSize * 2) + val campaignId = reader.readInt() + val backerScriptBytesLen = reader.readInt() + val backerScriptBytes = reader.read(backerScriptBytesLen).toArray + val projectScriptBytesLen = reader.readInt() + val projectScriptBytes = reader.read(projectScriptBytesLen).toArray + val deadline = reader.readInt() + val toRaise = reader.readLong() + + val backerScript = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(backerScriptBytes) + val projectScript = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(projectScriptBytes) + Pledge(pledgeId, campaignId, backerScript, projectScript, deadline, toRaise) + } + } + } object DatabaseStructures { @@ -115,6 +159,9 @@ object DatabaseStructures { // Campaigns val campaigns = memory.Map[CampaignId, Campaign, Nothing, Glass]() + + // Pledges + val pledges = memory.Map[PledgeId, Pledge, Nothing, Glass]() } object Scanner extends App with ScorexLogging { @@ -123,6 +170,9 @@ object Scanner extends App with ScorexLogging { import DatabaseStructures._ import ErgoTree.fromProposition + // used in methods producing transactions + implicit val me = ErgoAddressEncoder.apply(ErgoAddressEncoder.MainnetNetworkPrefix) + def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) val controlBoxNftIdString = "baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74cf99a" @@ -251,9 +301,9 @@ object Scanner extends App with ScorexLogging { val deadline = pledgeBox.get(R7).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] val minToRaise = pledgeBox.get(R8).get.asInstanceOf[LongConstant].value.asInstanceOf[Long] - // todo: pledge structure - + val pledge = Pledge(Base16.encode(pledgeBox.id), campaignId, backerScriptTree, projectScriptTree, deadline, minToRaise) log.info("Registered pledge box: " + boxId) + pledges.put(pledge.pledgeId, pledge) case i: Int if i == campaignScanId => val campaignBox = out.output @@ -268,6 +318,7 @@ object Scanner extends App with ScorexLogging { val campaign = Campaign(campaignId, campaignDesc, campaignScriptTree, campaignDeadline, campaignToRaise) log.info(s"Registered campaign box: $boxId, campaign: $campaign") + campaigns.put(campaign.id, campaign) case i: Int if i == paymentScanId => paymentBoxes.put(boxId, out.output.bytes) @@ -276,20 +327,22 @@ object Scanner extends App with ScorexLogging { } } + def makePledge(): Unit = { + //todo: finish + } + def registerCampaign(currentHeight: Int, campaignId: Int, campaignDesc: String, campaignScript: SigmaPropConstant, deadline: Int, minToRaise: Long): Unit = { - val fee = 10000000 //0.01 ERG def deductToken(tokens: Coll[(TokenId, Long)], index: Int): Coll[(TokenId, Long)] = { val atIdx = tokens.apply(index) tokens.updated(index, atIdx._1 -> (atIdx._2 - 1)) } - implicit val me = ErgoAddressEncoder.apply(ErgoAddressEncoder.MainnetNetworkPrefix) val keyBytes = Base16.decode("").get val key = DLogProverInput(BigIntegers.fromUnsignedByteArray(keyBytes)) From 75185629e5f8b8f9ab5e7374d9da06dbec46e4c0 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Fri, 23 Jul 2021 14:02:06 +0300 Subject: [PATCH 05/11] fix for campaign scan --- build.sbt | 11 +- .../org/ergoplatform/scanner/HttpServer.scala | 33 ++++ .../org/ergoplatform/scanner/Scanner.scala | 143 +++++++++++++----- 3 files changed, 147 insertions(+), 40 deletions(-) create mode 100644 src/main/scala/org/ergoplatform/scanner/HttpServer.scala diff --git a/build.sbt b/build.sbt index 5d30f13..e48d908 100644 --- a/build.sbt +++ b/build.sbt @@ -23,4 +23,13 @@ libraryDependencies ++= Seq( libraryDependencies += "org.ergoplatform" %% "ergo" % "v4.0.13-5251a78b-SNAPSHOT" // swaydb just for an experiment -libraryDependencies += "io.swaydb" %% "swaydb" % "0.16.2" \ No newline at end of file +libraryDependencies += "io.swaydb" %% "swaydb" % "0.16.2" + +// akka http for API +val AkkaVersion = "2.6.10" +val AkkaHttpVersion = "10.2.4" +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion, + "com.typesafe.akka" %% "akka-stream" % AkkaVersion, + "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion +) \ No newline at end of file diff --git a/src/main/scala/org/ergoplatform/scanner/HttpServer.scala b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala new file mode 100644 index 0000000..66e4dc7 --- /dev/null +++ b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala @@ -0,0 +1,33 @@ +package org.ergoplatform.scanner + +import akka.actor.typed.ActorSystem +import akka.actor.typed.scaladsl.Behaviors +import akka.http.scaladsl.Http +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.Directives._ +import scala.io.StdIn + +object HttpServerRoutingMinimal { + + def main(args: Array[String]): Unit = { + + implicit val system = ActorSystem(Behaviors.empty, "my-system") + // needed for the future flatMap/onComplete in the end + implicit val executionContext = system.executionContext + + val route = + path("campaigns") { + get { + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, DatabaseStructures.campaigns.count.toString)) + } + } + + val bindingFuture = Http().newServerAt("localhost", 7777).bind(route) + + println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") + StdIn.readLine() // let it run until user presses return + bindingFuture + .flatMap(_.unbind()) // trigger unbinding from the port + .onComplete(_ => system.terminate()) // and shutdown when done + } +} diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index e25c554..b74b2b8 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -34,6 +34,8 @@ import special.sigma.SigmaProp import swaydb.data.slice.Slice import swaydb.serializers.Serializer +import scala.util.Try + object BasicDataStructures { type ScanId = Int type Identifier = String @@ -143,25 +145,24 @@ object DatabaseStructures { import swaydb._ import swaydb.serializers.Default._ - import scala.concurrent.duration._ import org.ergoplatform.scanner.ErgoFundStructures._ // Create an in-memory map instance - val pledgeBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + val pledgeBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/pledgeBoxes") - val campaignBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + val campaignBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/campaignBoxes") - val paymentBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + val paymentBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/paymentBoxes") - val systemBoxes = memory.Map[String, Array[Byte], Nothing, Glass]() + val systemBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/systemBoxes") - val nftIndex = memory.Map[String, String, Nothing, Glass]() + val nftIndex = persistent.Map[String, String, Nothing, Glass](dir = "/tmp/data/nftIndex") // Campaigns - val campaigns = memory.Map[CampaignId, Campaign, Nothing, Glass]() + val campaigns = persistent.Map[CampaignId, Campaign, Nothing, Glass](dir = "/tmp/data/campaigns") // Pledges - val pledges = memory.Map[PledgeId, Pledge, Nothing, Glass]() + val pledges = persistent.Map[PledgeId, Pledge, Nothing, Glass](dir = "/tmp/data/pledges") } object Scanner extends App with ScorexLogging { @@ -170,8 +171,12 @@ object Scanner extends App with ScorexLogging { import DatabaseStructures._ import ErgoTree.fromProposition - // used in methods producing transactions + // used in methods producing and signing transactions + + implicit val ir = new RuntimeIRContext implicit val me = ErgoAddressEncoder.apply(ErgoAddressEncoder.MainnetNetworkPrefix) + val txFee = 10000000 //0.01 ERG + val settings = ErgoSettings.read() def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) @@ -307,19 +312,26 @@ object Scanner extends App with ScorexLogging { case i: Int if i == campaignScanId => val campaignBox = out.output - val value = campaignBox.value // todo: check the valur is above threshold - val campaignId = campaignBox.get(R4).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] - val campaignDescBytes = campaignBox.get(R5).get.asInstanceOf[CollectionConstant[SByte.type]].value.asInstanceOf[Array[Byte]] - val campaignDesc = new String(campaignDescBytes, "UTF-8") - val campaignScriptProp = campaignBox.get(R6).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] - val campaignScriptTree = ErgoTree.fromProposition(campaignScriptProp) - val campaignDeadline = campaignBox.get(R7).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] - val campaignToRaise = campaignBox.get(R8).get.asInstanceOf[LongConstant].value.asInstanceOf[Long] - - val campaign = Campaign(campaignId, campaignDesc, campaignScriptTree, campaignDeadline, campaignToRaise) - log.info(s"Registered campaign box: $boxId, campaign: $campaign") - campaigns.put(campaign.id, campaign) - + val value = campaignBox.value + if (value >= 1000000000L) { + val res = Try { + val campaignId = campaignBox.get(R4).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + val campaignDescBytes = campaignBox.get(R5).get.asInstanceOf[CollectionConstant[SByte.type]].value.toArray.asInstanceOf[Array[Byte]] + val campaignDesc = new String(campaignDescBytes, "UTF-8") + val campaignScriptProp = campaignBox.get(R6).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] + val campaignScriptTree = ErgoTree.fromProposition(campaignScriptProp) + val campaignDeadline = campaignBox.get(R7).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] + val campaignToRaise = campaignBox.get(R8).get.asInstanceOf[LongConstant].value.asInstanceOf[Long] + + val campaign = Campaign(campaignId, campaignDesc, campaignScriptTree, campaignDeadline, campaignToRaise) + log.info(s"Registered campaign box: $boxId, campaign: $campaign") + campaigns.put(campaign.id, campaign) + } + if (res.isFailure) { + val t = res.toEither.left.get + log.warn("Campaign box parsing failed: ", t) + } + } case i: Int if i == paymentScanId => paymentBoxes.put(boxId, out.output.bytes) log.info("Registered payment box: " + boxId) @@ -327,9 +339,46 @@ object Scanner extends App with ScorexLogging { } } - def makePledge(): Unit = { - //todo: finish - } + /* + def makePledge(amount: Long, + campaign: Campaign, + backerScript: SigmaPropConstant, + currentHeight: Int): Unit = { + + val inputKey = ??? + // todo: finish + + // see EIP-0018 + val pledgeScriptAddress = "XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16" + + val regs = Map( + R4 -> IntConstant(campaign.id), + R5 -> backerScript, + R6 -> campaign.script.toProposition(true).asInstanceOf[SigmaPropConstant], + R7 -> IntConstant(campaign.deadline), + R8 -> LongConstant(campaign.toRaise) + ) + + val pledgeOutput = new ErgoBoxCandidate( + amount, + me.fromString(pledgeScriptAddress).get.script, + currentHeight, + additionalTokens = Colls.emptyColl, + additionalRegisters = regs) + + val outputs = IndexedSeq[ErgoBoxCandidate](pledgeOutput, changeOutput, feeOutput) + + val inputs = inputBoxes.map(b => new UnsignedInput(b.id, ContextExtension.empty)).toIndexedSeq + val dataInputs = IndexedSeq.empty + val unsignedTx = UnsignedErgoTransaction(inputs, dataInputs, outputs) + + val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(inputKey)), LaunchParameters) + val tx = prover.sign(unsignedTx, inputBoxes.toIndexedSeq, IndexedSeq(controlBox), + ErgoStateContext.empty(settings), TransactionHintsBag.empty).get + val json = ErgoTransaction.ergoLikeTransactionEncoder(tx) + val txId = postJson(serverUrl + "transactions", json) + println("tx id: " + txId) + }*/ def registerCampaign(currentHeight: Int, campaignId: Int, @@ -343,8 +392,8 @@ object Scanner extends App with ScorexLogging { tokens.updated(index, atIdx._1 -> (atIdx._2 - 1)) } - val keyBytes = Base16.decode("").get - val key = DLogProverInput(BigIntegers.fromUnsignedByteArray(keyBytes)) + val campaignkeyBytes = Base16.decode("").get + val campaignKey = DLogProverInput(BigIntegers.fromUnsignedByteArray(campaignkeyBytes)) val controlBoxId = nftIndex.get(controlBoxNftIdString).get val controlBoxBytes = systemBoxes.get(controlBoxId).get @@ -355,7 +404,7 @@ object Scanner extends App with ScorexLogging { val tokensaleBoxId = nftIndex.get(tokenSaleNftIdString).get val tokensaleBoxBytes = systemBoxes.get(tokensaleBoxId).get val tokensaleBox = ErgoBoxSerializer.parseBytes(tokensaleBoxBytes) - val tokansaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, + val tokensaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, deductToken(tokensaleBox.additionalTokens, 1), tokensaleBox.additionalRegisters) val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] @@ -371,15 +420,15 @@ object Scanner extends App with ScorexLogging { val box = ErgoBoxSerializer.parseBytes(boxBytes) inputBoxes += box collectedAmount += box.value - collectedAmount < tokensaleBox.value + price + fee + collectedAmount < tokensaleBox.value + price + txFee }.count log.info("inputs: " + inputsCount) log.info("Collected payments: " + collectedAmount) log.info("price: " + price) - log.info("price + fee: " + (price + fee)) + log.info("price + fee: " + (price + txFee)) - if (price + fee > collectedAmount) { + if (price + txFee > collectedAmount) { throw new Exception("price + fee > collectedAmount") } @@ -404,19 +453,15 @@ object Scanner extends App with ScorexLogging { additionalTokens = Colls.fromArray(campaignTokens), additionalRegisters = regs) - val feeAmount = 2000000L // 0.002 ERG - val feeOutput = new ErgoBoxCandidate(feeAmount, ErgoScriptPredef.feeProposition(), currentHeight) + val feeOutput = new ErgoBoxCandidate(txFee, ErgoScriptPredef.feeProposition(), currentHeight) - val changeAmount = collectedAmount - campaignAmt - feeAmount - tokensaleBox.value - price + val changeAmount = collectedAmount - campaignAmt - txFee - tokensaleBox.value - price val changeOutput = new ErgoBoxCandidate(changeAmount, paymentTree, currentHeight) - val outputs = IndexedSeq[ErgoBoxCandidate](tokansaleOutput, devRewardOutput, campaignOutput, changeOutput, feeOutput) + val outputs = IndexedSeq[ErgoBoxCandidate](tokensaleOutput, devRewardOutput, campaignOutput, changeOutput, feeOutput) val unsignedTx = UnsignedErgoTransaction(inputs, dataInputs, outputs) - implicit val ir = new RuntimeIRContext - val settings = ErgoSettings.read() - - val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(key)), LaunchParameters) + val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(campaignKey)), LaunchParameters) val tx = prover.sign(unsignedTx, inputBoxes.toIndexedSeq, IndexedSeq(controlBox), ErgoStateContext.empty(settings), TransactionHintsBag.empty).get val json = ErgoTransaction.ergoLikeTransactionEncoder(tx) @@ -471,3 +516,23 @@ object Scanner extends App with ScorexLogging { step() } + + +object Tests extends App { + //todo: move to proper tests + + val campaignScript = SigmaPropConstant( + ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + .fromString("9f2UU1Jo52WDMCCCu9GYdiWSmb5V7jzP8FdRkVqqvCHyD72gTTR") + .get.asInstanceOf[P2PKAddress].pubkey + ) + val campaign = Campaign(4, "cmp", campaignScript, 500, 2000000000L) + + val ser = ErgoFundStructures.campaignSerialiser + val cbs = ser.write(campaign) + + assert(ser.read(cbs) == campaign) + + println(campaign.script.toProposition(true).asInstanceOf[SigmaPropConstant]) + +} \ No newline at end of file From 02b086fbfa2d828d97b2cdf66cc0aa1d05681d6a Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 27 Jul 2021 23:43:11 +0300 Subject: [PATCH 06/11] updating initial scanning height --- .../org/ergoplatform/scanner/Scanner.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index b74b2b8..6aa93d8 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -23,7 +23,7 @@ import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ErgoTree, IntCo import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval.RuntimeIRContext import sigmastate.interpreter.ContextExtension -import sigmastate.serialization.ErgoTreeSerializer +import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} import special.collection.Coll import sigmastate.eval.Colls @@ -219,14 +219,14 @@ object Scanner extends App with ScorexLogging { val serverUrl = "http://213.239.193.208:9053/" - val bestChainHeaderIds = mutable.Map[Int, Identifier](516000 -> "1a9cad7745fb39ba30b6d1a43f524eb43870ddf31bd04f6ac7a3b95f1a23f8a0") + val bestChainHeaderIds = mutable.Map[Int, Identifier](538469 -> "c8596d39793da6d35241183dc45b074a68339660f835dd76708684fe0268f1e2") private def getJsonAsString(url: String): String = { Http(s"$url") .header("Content-Type", "application/json") .header("Accept", "application/json") .header("Charset", "UTF-8") - .option(HttpOptions.readTimeout(120000)) + .option(HttpOptions.readTimeout(180000)) .asString .body } @@ -237,7 +237,7 @@ object Scanner extends App with ScorexLogging { .header("Accept", "application/json") .header("Charset", "UTF-8") .postData(json.toString()) - .option(HttpOptions.readTimeout(120000)) + .option(HttpOptions.readTimeout(180000)) .asString .body } @@ -470,7 +470,7 @@ object Scanner extends App with ScorexLogging { } - var lastHeight = 516000 // we start from some recent block + var lastHeight = bestChainHeaderIds.last._1 // we start from some recent block @tailrec def step(): Unit = { @@ -518,6 +518,12 @@ object Scanner extends App with ScorexLogging { } + + + + + + object Tests extends App { //todo: move to proper tests @@ -535,4 +541,6 @@ object Tests extends App { println(campaign.script.toProposition(true).asInstanceOf[SigmaPropConstant]) + + println(Base16.encode(ValueSerializer.serialize(LongConstant(1000000)))) } \ No newline at end of file From 8abf619df48a1db41ed6c7aa101ced5e504f88e3 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 29 Jul 2021 00:37:24 +0300 Subject: [PATCH 07/11] campaignId increase --- .../scala/org/ergoplatform/scanner/Scanner.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 6aa93d8..1bbe92e 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -63,7 +63,7 @@ object ErgoFundStructures { case class Pledge(pledgeId: PledgeId, campaignId: CampaignId, backerScript: ErgoTree, projectScript: ErgoTree, deadline: Int, toRaise: Long) - //binary campaign data serializer for SwayDB + //binary serializers for SwayDB below implicit val campaignSerialiser = new Serializer[Campaign] { override def write(cmp: Campaign): Slice[Byte] = { @@ -98,7 +98,6 @@ object ErgoFundStructures { } } - //binary campaign data serializer for SwayDB implicit val pledgeSerialiser = new Serializer[Pledge] { override def write(pl: Pledge): Slice[Byte] = { @@ -147,9 +146,10 @@ object DatabaseStructures { import swaydb.serializers.Default._ import org.ergoplatform.scanner.ErgoFundStructures._ - // Create an in-memory map instance + // for boxes corresponding to pledges val pledgeBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/pledgeBoxes") + // for boxes corresponding to campaigns val campaignBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/campaignBoxes") val paymentBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/paymentBoxes") @@ -381,7 +381,6 @@ object Scanner extends App with ScorexLogging { }*/ def registerCampaign(currentHeight: Int, - campaignId: Int, campaignDesc: String, campaignScript: SigmaPropConstant, deadline: Int, @@ -406,6 +405,7 @@ object Scanner extends App with ScorexLogging { val tokensaleBox = ErgoBoxSerializer.parseBytes(tokensaleBoxBytes) val tokensaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, deductToken(tokensaleBox.additionalTokens, 1), tokensaleBox.additionalRegisters) + val campaignId = tokensaleBox.additionalRegisters(R4).asInstanceOf[IntConstant].value.asInstanceOf[Int] val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] val devRewardScriptBytes = controlBox.additionalRegisters(R5).asInstanceOf[CollectionConstant[SByte.type]].value.asInstanceOf[Coll[Byte]].toArray @@ -451,7 +451,7 @@ object Scanner extends App with ScorexLogging { campaignAddress, currentHeight, additionalTokens = Colls.fromArray(campaignTokens), - additionalRegisters = regs) + additionalRegisters = Map(R4 -> IntConstant(campaignId + 1))) val feeOutput = new ErgoBoxCandidate(txFee, ErgoScriptPredef.feeProposition(), currentHeight) @@ -503,7 +503,7 @@ object Scanner extends App with ScorexLogging { val deadline = 550000 val minToRaise = 100000000000L - // registerCampaign(lastHeight, campaignId, campaignDesc, campaignScript, deadline, minToRaise) + // registerCampaign(lastHeight, campaignDesc, campaignScript, deadline, minToRaise) Thread.sleep(60 * 1000) // 1 minute step() } From cb5c06c08b1b78052503a9b1577585fb31e8f032 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 2 Aug 2021 01:39:51 +0300 Subject: [PATCH 08/11] more comments, pledges route --- .../org/ergoplatform/scanner/HttpServer.scala | 17 ++++++++++++----- .../org/ergoplatform/scanner/Scanner.scala | 9 ++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/HttpServer.scala b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala index 66e4dc7..b657f03 100644 --- a/src/main/scala/org/ergoplatform/scanner/HttpServer.scala +++ b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala @@ -15,12 +15,19 @@ object HttpServerRoutingMinimal { // needed for the future flatMap/onComplete in the end implicit val executionContext = system.executionContext - val route = - path("campaigns") { - get { - complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, DatabaseStructures.campaigns.count.toString)) - } + val campaignsRoute = path("campaigns") { + get { + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, DatabaseStructures.campaigns.count.toString)) } + } + + val pledgesRoute = path("pledges") { + get { + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, DatabaseStructures.pledges.count.toString)) + } + } + + val route = campaignsRoute ~ pledgesRoute val bindingFuture = Http().newServerAt("localhost", 7777).bind(route) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 1bbe92e..592a402 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -152,10 +152,14 @@ object DatabaseStructures { // for boxes corresponding to campaigns val campaignBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/campaignBoxes") + // boxes used by offchain service to pay for campaign registration. In the real deployed ErgoFund likely + // a user should pay via a proxy service or DApp connector val paymentBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/paymentBoxes") + // control box and tokensale box stored there val systemBoxes = persistent.Map[String, Array[Byte], Nothing, Glass](dir = "/tmp/data/systemBoxes") + // actual NftId -> BoxId correspondences val nftIndex = persistent.Map[String, String, Nothing, Glass](dir = "/tmp/data/nftIndex") // Campaigns @@ -200,11 +204,13 @@ object Scanner extends App with ScorexLogging { val pledgeScan = Scan(pledgeScanId, EqualsScanningPredicate(ErgoBox.R1, Values.ByteArrayConstant(pledgeScriptBytes))) // We scan for ErgoFund campaign data, stored in outputs with the ErgoFund token + // The scan will find tokensale boxes also, we filter them out later val campaignTokenId = Base16.decode("07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce").get val campaignScanId = 4 val campaignScan = Scan(campaignScanId, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) - // + // Script of boxes used to pay for registering campaigns. In deployed ErgoFund should be replaced with a user paying + // via proxy service or DApp connector. val paymentTree = ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) .fromString("9f2UU1Jo52WDMCCCu9GYdiWSmb5V7jzP8FdRkVqqvCHyD72gTTR") .get @@ -279,6 +285,7 @@ object Scanner extends App with ScorexLogging { ExtractionResult(Seq.empty, createdOutputs) } + // ErgoFund-specific logic to process scans-related data extracted from the blockchain def ergoFundProcess(extractionResult: BasicDataStructures.ExtractionResult): Unit = { extractionResult.createdOutputs.foreach { out => val boxId = bytesToString(out.output.id) From 52d1a5c7520e3c89e2ec40e6ee815e5e9414c939 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 10 Aug 2021 18:55:59 +0300 Subject: [PATCH 09/11] CSigmaProp for dev script --- .../org/ergoplatform/scanner/Scanner.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index 592a402..a43def6 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -19,7 +19,7 @@ import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import scorex.util.encode.Base16 import sigmastate.{SByte, Values} -import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ErgoTree, IntConstant, LongConstant, SigmaPropConstant} +import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ConstantNode, ErgoTree, IntConstant, LongConstant, SigmaPropConstant} import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval.RuntimeIRContext import sigmastate.interpreter.ContextExtension @@ -184,12 +184,12 @@ object Scanner extends App with ScorexLogging { def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) - val controlBoxNftIdString = "baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74cf99a" + val controlBoxNftIdString = "c88bd2971e56e0c73d48d7fdc466efe068a4b64cdf19b5ee9f38f1cd850de33e" val controlBoxNftId = Base16.decode(controlBoxNftIdString).get val controlBoxScanId = 1 val controlBoxScan = Scan(controlBoxScanId, ContainsAssetPredicate(Digest32 @@ controlBoxNftId)) - val tokenSaleNftIdString = "48ab32adaf7eb132c76c077fdeb3df6a6692417168f1541c83a5545c10c63f81" + val tokenSaleNftIdString = "1e24b4ec7c244962737d3f58ec83a444aa0fd45f2505b409ea4b3c9ba8427b12" val tokenSaleNftId = Base16.decode(tokenSaleNftIdString).get val tokenSaleScanId = 2 val tokenSaleScan = Scan(tokenSaleScanId, ContainsAssetPredicate(Digest32 @@ tokenSaleNftId)) @@ -205,7 +205,7 @@ object Scanner extends App with ScorexLogging { // We scan for ErgoFund campaign data, stored in outputs with the ErgoFund token // The scan will find tokensale boxes also, we filter them out later - val campaignTokenId = Base16.decode("07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce").get + val campaignTokenId = Base16.decode("7b6a228b323ede2361c02d7adf66224c79b6a2d2075e63834b98886e4004802f").get val campaignScanId = 4 val campaignScan = Scan(campaignScanId, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) @@ -225,7 +225,7 @@ object Scanner extends App with ScorexLogging { val serverUrl = "http://213.239.193.208:9053/" - val bestChainHeaderIds = mutable.Map[Int, Identifier](538469 -> "c8596d39793da6d35241183dc45b074a68339660f835dd76708684fe0268f1e2") + val bestChainHeaderIds = mutable.Map[Int, Identifier](551820 -> "bab7de083fa4bc7152d88bab373187a39839a941ac84437201707581bf0e05c5") private def getJsonAsString(url: String): String = { Http(s"$url") @@ -415,8 +415,7 @@ object Scanner extends App with ScorexLogging { val campaignId = tokensaleBox.additionalRegisters(R4).asInstanceOf[IntConstant].value.asInstanceOf[Int] val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] - val devRewardScriptBytes = controlBox.additionalRegisters(R5).asInstanceOf[CollectionConstant[SByte.type]].value.asInstanceOf[Coll[Byte]].toArray - val devRewardScript = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(devRewardScriptBytes) + val devRewardScript = controlBox.additionalRegisters(R5).asInstanceOf[CSigmaProp].sigmaTree val devRewardOutput = new ErgoBoxCandidate(price, devRewardScript, currentHeight) val inputBoxes = mutable.Buffer[ErgoBox]() @@ -508,9 +507,9 @@ object Scanner extends App with ScorexLogging { .get.asInstanceOf[P2PKAddress].pubkey ) - val deadline = 550000 + val deadline = 555000 val minToRaise = 100000000000L - // registerCampaign(lastHeight, campaignDesc, campaignScript, deadline, minToRaise) + registerCampaign(lastHeight, campaignDesc, campaignScript, deadline, minToRaise) Thread.sleep(60 * 1000) // 1 minute step() } From 9735b1edade935258283e41f4d06424c9798ed9b Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 10 Aug 2021 19:46:12 +0300 Subject: [PATCH 10/11] r5 type fix --- src/main/scala/org/ergoplatform/scanner/Scanner.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index a43def6..d62e0fb 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -18,7 +18,7 @@ import org.ergoplatform.wallet.secrets.PrimitiveSecretKey import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import scorex.util.encode.Base16 -import sigmastate.{SByte, Values} +import sigmastate.{SByte, SSigmaProp, Values} import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ConstantNode, ErgoTree, IntConstant, LongConstant, SigmaPropConstant} import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval.RuntimeIRContext @@ -415,7 +415,7 @@ object Scanner extends App with ScorexLogging { val campaignId = tokensaleBox.additionalRegisters(R4).asInstanceOf[IntConstant].value.asInstanceOf[Int] val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] - val devRewardScript = controlBox.additionalRegisters(R5).asInstanceOf[CSigmaProp].sigmaTree + val devRewardScript = controlBox.additionalRegisters(R5).asInstanceOf[ConstantNode[SSigmaProp.type]].value.asInstanceOf[CSigmaProp].sigmaTree val devRewardOutput = new ErgoBoxCandidate(price, devRewardScript, currentHeight) val inputBoxes = mutable.Buffer[ErgoBox]() From e47f389fab977d4c29baee805be8b3eee87587b8 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Fri, 13 Aug 2021 01:02:45 +0300 Subject: [PATCH 11/11] campaign tregistration fix --- .../org/ergoplatform/scanner/HttpServer.scala | 2 +- .../org/ergoplatform/scanner/Scanner.scala | 65 ++++--------------- 2 files changed, 15 insertions(+), 52 deletions(-) diff --git a/src/main/scala/org/ergoplatform/scanner/HttpServer.scala b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala index b657f03..f205d67 100644 --- a/src/main/scala/org/ergoplatform/scanner/HttpServer.scala +++ b/src/main/scala/org/ergoplatform/scanner/HttpServer.scala @@ -31,7 +31,7 @@ object HttpServerRoutingMinimal { val bindingFuture = Http().newServerAt("localhost", 7777).bind(route) - println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") + println(s"Server online at http://localhost:7777/\nPress RETURN to stop...") StdIn.readLine() // let it run until user presses return bindingFuture .flatMap(_.unbind()) // trigger unbinding from the port diff --git a/src/main/scala/org/ergoplatform/scanner/Scanner.scala b/src/main/scala/org/ergoplatform/scanner/Scanner.scala index d62e0fb..76f4317 100644 --- a/src/main/scala/org/ergoplatform/scanner/Scanner.scala +++ b/src/main/scala/org/ergoplatform/scanner/Scanner.scala @@ -184,12 +184,12 @@ object Scanner extends App with ScorexLogging { def bytesToString(bs: Array[Byte]): String = Base16.encode(bs) - val controlBoxNftIdString = "c88bd2971e56e0c73d48d7fdc466efe068a4b64cdf19b5ee9f38f1cd850de33e" + val controlBoxNftIdString = "72c3fbce3243d491d81eb564cdab1662b1f8d4c7e312b88870cec79b7cfd4321" val controlBoxNftId = Base16.decode(controlBoxNftIdString).get val controlBoxScanId = 1 val controlBoxScan = Scan(controlBoxScanId, ContainsAssetPredicate(Digest32 @@ controlBoxNftId)) - val tokenSaleNftIdString = "1e24b4ec7c244962737d3f58ec83a444aa0fd45f2505b409ea4b3c9ba8427b12" + val tokenSaleNftIdString = "15b0ae41c24230069ff96dacbac0932850ac0c2a0924daf72a39e88cbcf3acd5" val tokenSaleNftId = Base16.decode(tokenSaleNftIdString).get val tokenSaleScanId = 2 val tokenSaleScan = Scan(tokenSaleScanId, ContainsAssetPredicate(Digest32 @@ tokenSaleNftId)) @@ -205,7 +205,7 @@ object Scanner extends App with ScorexLogging { // We scan for ErgoFund campaign data, stored in outputs with the ErgoFund token // The scan will find tokensale boxes also, we filter them out later - val campaignTokenId = Base16.decode("7b6a228b323ede2361c02d7adf66224c79b6a2d2075e63834b98886e4004802f").get + val campaignTokenId = Base16.decode("05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563d65c9622c43a659").get val campaignScanId = 4 val campaignScan = Scan(campaignScanId, ContainsAssetPredicate(Digest32 @@ campaignTokenId)) @@ -225,7 +225,7 @@ object Scanner extends App with ScorexLogging { val serverUrl = "http://213.239.193.208:9053/" - val bestChainHeaderIds = mutable.Map[Int, Identifier](551820 -> "bab7de083fa4bc7152d88bab373187a39839a941ac84437201707581bf0e05c5") + val bestChainHeaderIds = mutable.Map[Int, Identifier](553033 -> "447779fe8f73fd3b5c90ae063099342480ae03d963662675a2ce4f79081ecd6e") private def getJsonAsString(url: String): String = { Http(s"$url") @@ -302,7 +302,6 @@ object Scanner extends App with ScorexLogging { case i: Int if i == pledgeScanId => val pledgeBox = out.output - val value = pledgeBox.value val campaignId = pledgeBox.get(R4).get.asInstanceOf[IntConstant].value.asInstanceOf[Int] val backerScriptProp = pledgeBox.get(R5).get.asInstanceOf[SigmaPropConstant].value.asInstanceOf[SigmaProp] @@ -346,46 +345,6 @@ object Scanner extends App with ScorexLogging { } } - /* - def makePledge(amount: Long, - campaign: Campaign, - backerScript: SigmaPropConstant, - currentHeight: Int): Unit = { - - val inputKey = ??? - // todo: finish - - // see EIP-0018 - val pledgeScriptAddress = "XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16" - - val regs = Map( - R4 -> IntConstant(campaign.id), - R5 -> backerScript, - R6 -> campaign.script.toProposition(true).asInstanceOf[SigmaPropConstant], - R7 -> IntConstant(campaign.deadline), - R8 -> LongConstant(campaign.toRaise) - ) - - val pledgeOutput = new ErgoBoxCandidate( - amount, - me.fromString(pledgeScriptAddress).get.script, - currentHeight, - additionalTokens = Colls.emptyColl, - additionalRegisters = regs) - - val outputs = IndexedSeq[ErgoBoxCandidate](pledgeOutput, changeOutput, feeOutput) - - val inputs = inputBoxes.map(b => new UnsignedInput(b.id, ContextExtension.empty)).toIndexedSeq - val dataInputs = IndexedSeq.empty - val unsignedTx = UnsignedErgoTransaction(inputs, dataInputs, outputs) - - val prover = new ErgoProvingInterpreter(IndexedSeq(PrimitiveSecretKey(inputKey)), LaunchParameters) - val tx = prover.sign(unsignedTx, inputBoxes.toIndexedSeq, IndexedSeq(controlBox), - ErgoStateContext.empty(settings), TransactionHintsBag.empty).get - val json = ErgoTransaction.ergoLikeTransactionEncoder(tx) - val txId = postJson(serverUrl + "transactions", json) - println("tx id: " + txId) - }*/ def registerCampaign(currentHeight: Int, campaignDesc: String, @@ -405,15 +364,18 @@ object Scanner extends App with ScorexLogging { val controlBoxBytes = systemBoxes.get(controlBoxId).get val controlBox = ErgoBoxSerializer.parseBytes(controlBoxBytes) + println("Control box: " + controlBox) log.info("Control box ID: " + controlBoxId) val tokensaleBoxId = nftIndex.get(tokenSaleNftIdString).get val tokensaleBoxBytes = systemBoxes.get(tokensaleBoxId).get val tokensaleBox = ErgoBoxSerializer.parseBytes(tokensaleBoxBytes) - val tokensaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, - deductToken(tokensaleBox.additionalTokens, 1), tokensaleBox.additionalRegisters) val campaignId = tokensaleBox.additionalRegisters(R4).asInstanceOf[IntConstant].value.asInstanceOf[Int] + val updTokensaleRegs = tokensaleBox.additionalRegisters.updated(R4, IntConstant(campaignId + 1)) + val tokensaleOutput = new ErgoBoxCandidate(tokensaleBox.value, tokensaleBox.ergoTree, currentHeight, + deductToken(tokensaleBox.additionalTokens, 1), updTokensaleRegs) + val price = controlBox.additionalRegisters(R4).asInstanceOf[LongConstant].value.asInstanceOf[Long] val devRewardScript = controlBox.additionalRegisters(R5).asInstanceOf[ConstantNode[SSigmaProp.type]].value.asInstanceOf[CSigmaProp].sigmaTree val devRewardOutput = new ErgoBoxCandidate(price, devRewardScript, currentHeight) @@ -457,7 +419,7 @@ object Scanner extends App with ScorexLogging { campaignAddress, currentHeight, additionalTokens = Colls.fromArray(campaignTokens), - additionalRegisters = Map(R4 -> IntConstant(campaignId + 1))) + additionalRegisters = regs) val feeOutput = new ErgoBoxCandidate(txFee, ErgoScriptPredef.feeProposition(), currentHeight) @@ -499,7 +461,6 @@ object Scanner extends App with ScorexLogging { step() case None => log.info(s"No block found @ height $newHeight") - val campaignId = 0 val campaignDesc = "Test campaign" val campaignScript = SigmaPropConstant( ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) @@ -509,7 +470,7 @@ object Scanner extends App with ScorexLogging { val deadline = 555000 val minToRaise = 100000000000L - registerCampaign(lastHeight, campaignDesc, campaignScript, deadline, minToRaise) + // registerCampaign(lastHeight, campaignDesc, campaignScript, deadline, minToRaise) Thread.sleep(60 * 1000) // 1 minute step() } @@ -548,5 +509,7 @@ object Tests extends App { println(campaign.script.toProposition(true).asInstanceOf[SigmaPropConstant]) - println(Base16.encode(ValueSerializer.serialize(LongConstant(1000000)))) + println(Base16.encode(ValueSerializer.serialize(LongConstant(100000000)))) + + println(Base16.encode(ValueSerializer.serialize(IntConstant(555000)))) } \ No newline at end of file