diff --git a/build.sbt b/build.sbt index b6116b4644..a72c075a74 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ val circeVersion = "0.13.0" val akkaVersion = "2.6.10" val akkaHttpVersion = "10.2.4" -val sigmaStateVersion = "5.0.10" +val sigmaStateVersion = "5.0.12" // for testing current sigmastate build (see sigmastate-ergo-it jenkins job) val effectiveSigmaStateVersion = Option(System.getenv().get("SIGMASTATE_VERSION")).getOrElse(sigmaStateVersion) diff --git a/ergo-wallet/src/main/java/org/ergoplatform/wallet/interface4j/crypto/ErgoUnsafeProver.java b/ergo-wallet/src/main/java/org/ergoplatform/wallet/interface4j/crypto/ErgoUnsafeProver.java index 4493e490e5..f12f4cb457 100644 --- a/ergo-wallet/src/main/java/org/ergoplatform/wallet/interface4j/crypto/ErgoUnsafeProver.java +++ b/ergo-wallet/src/main/java/org/ergoplatform/wallet/interface4j/crypto/ErgoUnsafeProver.java @@ -3,7 +3,7 @@ import org.ergoplatform.ErgoLikeTransaction; import org.ergoplatform.UnsignedErgoLikeTransaction; import scala.collection.JavaConverters; -import sigmastate.basics.DLogProtocol; +import sigmastate.crypto.DLogProtocol; import java.util.Map; /** diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala b/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala index 55a70b4016..7569b8c00c 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala @@ -8,7 +8,7 @@ import org.ergoplatform.{Height, MinerPubkey, Outputs, Self} import sigmastate.Values.{ByteArrayConstant, ErgoTree, IntConstant, LongConstant, SigmaPropValue, Value} import sigmastate.utxo._ import sigmastate._ -import special.collection.Coll +import sigma.Coll /** * Container for re-emission related contracts. Contains re-emission contract and pay-to-reemission contract. diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/ErgoBoxAssetExtractor.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/ErgoBoxAssetExtractor.scala index 8c2fdf5384..34dcdc1c74 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/ErgoBoxAssetExtractor.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/ErgoBoxAssetExtractor.scala @@ -2,7 +2,7 @@ package org.ergoplatform.wallet.boxes import java7.compat.Math import org.ergoplatform.ErgoBoxCandidate -import special.collection.Extensions._ +import sigma.Extensions._ import scala.collection.compat.immutable.ArraySeq import scala.collection.mutable diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala index 2cfec5ad29..472b54e9f8 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala @@ -8,7 +8,7 @@ import org.ergoplatform.wallet.serialization.ErgoWalletSerializer import org.ergoplatform.{ErgoBox, ErgoBoxAssets, ErgoLikeTransaction} import scorex.util.serialization.{Reader, Writer} import scorex.util.{ModifierId, bytesToId, idToBytes} -import special.collection.Extensions._ +import sigma.Extensions._ /** * A box tracked by a wallet that contains Ergo box itself as well as diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/crypto/ErgoSignature.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/crypto/ErgoSignature.scala index 03f144ea2d..430fc8c894 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/crypto/ErgoSignature.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/crypto/ErgoSignature.scala @@ -3,7 +3,7 @@ package org.ergoplatform.wallet.crypto import org.bouncycastle.util.BigIntegers import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 -import sigmastate.basics.CryptoConstants +import sigmastate.crypto.CryptoConstants import sigmastate.serialization.GroupElementSerializer import scala.annotation.tailrec diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoInterpreter.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoInterpreter.scala index e429ecbdad..bdd423615f 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoInterpreter.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoInterpreter.scala @@ -1,14 +1,14 @@ package org.ergoplatform.wallet.interpreter import org.ergoplatform.ErgoLikeContext.Height -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk.BlockchainParameters import org.ergoplatform.wallet.protocol.Constants import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoLikeContext, ErgoLikeInterpreter} import scorex.util.ScorexLogging import sigmastate.Values.ErgoTree import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} import sigmastate.{AvlTreeData, AvlTreeFlags} -import special.collection.Coll +import sigma.Coll import scala.util.Try @@ -18,7 +18,7 @@ import scala.util.Try * * @param params - current values of adjustable blockchain settings */ -class ErgoInterpreter(params: ErgoLikeParameters) +class ErgoInterpreter(params: BlockchainParameters) extends ErgoLikeInterpreter with ScorexLogging { /** Override default logging for all Ergo interpreters. */ @@ -96,7 +96,7 @@ object ErgoInterpreter { val interpreterInitCost = 10000 /** Creates an interpreter with the given parameters. */ - def apply(params: ErgoLikeParameters): ErgoInterpreter = + def apply(params: BlockchainParameters): ErgoInterpreter = new ErgoInterpreter(params) /** Create [[AvlTreeData]] with the given digest and all operations enabled. */ diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreter.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreter.scala index 4d1d62ed6f..62732ab788 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreter.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreter.scala @@ -1,19 +1,18 @@ package org.ergoplatform.wallet.interpreter import org.ergoplatform._ +import org.ergoplatform.sdk.BlockchainParameters import org.ergoplatform.sdk.utils.ArithUtils.{addExact, multiplyExact} -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import org.ergoplatform.sdk.wallet.secrets.{ExtendedPublicKey, ExtendedSecretKey, SecretKey} import org.ergoplatform.validation.{SigmaValidationSettings, ValidationRules} import org.ergoplatform.wallet.boxes.ErgoBoxAssetExtractor -import scorex.crypto.authds.ADDigest import scorex.util.encode.Base16 import sigmastate.AvlTreeData import sigmastate.Values.SigmaBoolean -import sigmastate.basics.SigmaProtocolPrivateInput +import sigmastate.crypto.SigmaProtocolPrivateInput import sigmastate.interpreter.{ContextExtension, ProverInterpreter} -import special.collection.Coll -import special.sigma.{Header, PreHeader} +import sigma.{Coll, Header, PreHeader} import java.util import scala.util.{Failure, Success, Try} @@ -39,14 +38,14 @@ import scala.util.{Failure, Success, Try} * (to not to recompute them) */ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], - params: ErgoLikeParameters, + params: BlockchainParameters, val cachedHdPubKeysOpt: Option[IndexedSeq[ExtendedPublicKey]] = None) extends ErgoInterpreter(params) with ProverInterpreter { /** * Interpreter's secrets, in form of sigma protocols private inputs */ - val secrets: IndexedSeq[SigmaProtocolPrivateInput[_, _]] = secretKeys.map(_.privateInput) + val secrets: IndexedSeq[SigmaProtocolPrivateInput[_]] = secretKeys.map(_.privateInput) /** * Only secrets corresponding to hierarchical deterministic scheme (BIP-32 impl) @@ -93,14 +92,14 @@ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], * @param newParams - updated parameters * @return modified prover */ - def withNewParameters(newParams: ErgoLikeParameters): ErgoProvingInterpreter = { + def withNewParameters(newParams: BlockchainParameters): ErgoProvingInterpreter = { new ErgoProvingInterpreter(this.secretKeys, newParams, this.cachedHdPubKeysOpt) } def signInputs(unsignedTx: UnsignedErgoLikeTransaction, boxesToSpend: IndexedSeq[ErgoBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, txHints: TransactionHintsBag): Try[(IndexedSeq[Input], Long)] = { if (unsignedTx.inputs.length != boxesToSpend.length) { Failure(new Exception("Not enough boxes to spend")) @@ -164,7 +163,7 @@ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], def sign(unsignedTx: UnsignedErgoLikeTransaction, boxesToSpend: IndexedSeq[ErgoBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, txHints: TransactionHintsBag = TransactionHintsBag.empty): Try[ErgoLikeTransaction] = { val signedInputs: Try[(IndexedSeq[Input], Long)] = @@ -190,7 +189,7 @@ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], def generateCommitmentsFor(unsignedTx: UnsignedErgoLikeTransaction, boxesToSpend: IndexedSeq[ErgoBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext): Try[TransactionHintsBag] = Try { + stateContext: BlockchainStateContext): Try[TransactionHintsBag] = Try { val inputCmts = unsignedTx.inputs.zipWithIndex.map { case (unsignedInput, inpIndex) => val inputBox = boxesToSpend(inpIndex) @@ -230,7 +229,7 @@ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], def bagForTransaction(tx: ErgoLikeTransaction, boxesToSpend: IndexedSeq[ErgoBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, realSecretsToExtract: Seq[SigmaBoolean], simulatedSecretsToExtract: Seq[SigmaBoolean]): TransactionHintsBag = { val augmentedInputs = tx.inputs.zipWithIndex.zip(boxesToSpend) @@ -262,11 +261,11 @@ class ErgoProvingInterpreter(val secretKeys: IndexedSeq[SecretKey], object ErgoProvingInterpreter { def apply(secrets: IndexedSeq[SecretKey], - params: ErgoLikeParameters): ErgoProvingInterpreter = + params: BlockchainParameters): ErgoProvingInterpreter = new ErgoProvingInterpreter(secrets, params) def apply(rootSecret: ExtendedSecretKey, - params: ErgoLikeParameters): ErgoProvingInterpreter = + params: BlockchainParameters): ErgoProvingInterpreter = new ErgoProvingInterpreter(IndexedSeq(rootSecret), params) } diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoUnsafeProver.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoUnsafeProver.scala index ad1f780404..c2b763cb65 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoUnsafeProver.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/interpreter/ErgoUnsafeProver.scala @@ -2,7 +2,7 @@ package org.ergoplatform.wallet.interpreter import org.ergoplatform.{ErgoLikeTransaction, Input, UnsignedErgoLikeTransaction} import scorex.util.encode.Base16 -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol.DLogProverInput import sigmastate.interpreter.{ContextExtension, ProverResult} /** diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala index 6ad66b5423..745002eacf 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala @@ -10,8 +10,8 @@ import scorex.util.{ModifierId, bytesToId} import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.utils.Extensions._ -import special.collection.Coll -import special.collection.Extensions._ +import sigma.Coll +import sigma.Extensions._ import scala.collection.JavaConverters._ import scala.util.Try diff --git a/ergo-wallet/src/test/java/org/ergoplatform/wallet/CreateTransactionDemo.java b/ergo-wallet/src/test/java/org/ergoplatform/wallet/CreateTransactionDemo.java index 11ac65f6b0..70b39eea0e 100644 --- a/ergo-wallet/src/test/java/org/ergoplatform/wallet/CreateTransactionDemo.java +++ b/ergo-wallet/src/test/java/org/ergoplatform/wallet/CreateTransactionDemo.java @@ -8,7 +8,7 @@ import org.ergoplatform.wallet.transactions.TransactionBuilder; import org.ergoplatform.wallet.transactions.TransactionBuilder.Payment; import scorex.util.Random; -import sigmastate.basics.DLogProtocol; +import sigmastate.crypto.DLogProtocol; import java.util.ArrayList; import java.util.HashMap; diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/boxes/DefaultBoxSelectorSpec.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/boxes/DefaultBoxSelectorSpec.scala index 311275b06f..f34f050501 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/boxes/DefaultBoxSelectorSpec.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/boxes/DefaultBoxSelectorSpec.scala @@ -16,7 +16,7 @@ import sigmastate.Values.SigmaPropValue import sigmastate.eval.Extensions._ import sigmastate.helpers.TestingHelpers._ import sigmastate.utils.Extensions._ -import special.collection.Extensions._ +import sigma.Extensions._ import scala.util.Random diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/crypto/ErgoSignatureSpec.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/crypto/ErgoSignatureSpec.scala index 42993ec9df..5c35807d5e 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/crypto/ErgoSignatureSpec.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/crypto/ErgoSignatureSpec.scala @@ -4,7 +4,7 @@ import org.ergoplatform.wallet.utils.Generators import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import scorex.util.Random -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol.DLogProverInput class ErgoSignatureSpec extends AnyPropSpec with Matchers with Generators { diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreterSpec.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreterSpec.scala index 0d96c4c8cc..e821987d01 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreterSpec.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/ErgoProvingInterpreterSpec.scala @@ -9,6 +9,7 @@ import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import scorex.util.{ModifierId, Random} import scorex.util.encode.Base16 +import sigma.Colls import sigmastate.CTHRESHOLD import sigmastate.Values.{GroupElementConstant, SigmaBoolean} import sigmastate.interpreter.{ContextExtension, HintsBag} diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/InterpreterSpecCommon.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/InterpreterSpecCommon.scala index 484c7fb47a..5f797a27d2 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/InterpreterSpecCommon.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/interpreter/InterpreterSpecCommon.scala @@ -1,16 +1,16 @@ package org.ergoplatform.wallet.interpreter -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext} +import org.ergoplatform.sdk.BlockchainParameters +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import scorex.util.encode.Base16 -import sigmastate.basics.CryptoConstants +import sigmastate.crypto.CryptoConstants import sigmastate.eval.Extensions.ArrayOps -import sigmastate.eval.{CGroupElement, CPreHeader, Colls} -import special.collection.Coll -import special.sigma.{Header, PreHeader} +import sigmastate.eval.{CGroupElement, CPreHeader} +import sigma.{Coll, Colls, Header, PreHeader} trait InterpreterSpecCommon { - protected val parameters = new ErgoLikeParameters { + protected val parameters = new BlockchainParameters { override def storageFeeFactor: Int = 1250000 @@ -35,7 +35,7 @@ trait InterpreterSpecCommon { override def blockVersion: Byte = 1 } - protected val stateContext = new ErgoLikeStateContext { + protected val stateContext = new BlockchainStateContext { override def sigmaLastHeaders: Coll[Header] = Colls.emptyColl diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/utils/Generators.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/utils/Generators.scala index fc8d6d3ab3..8ba31c3c27 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/utils/Generators.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/utils/Generators.scala @@ -13,7 +13,7 @@ import org.scalacheck.{Arbitrary, Gen} import scorex.crypto.authds.ADKey import scorex.util._ import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ErgoTree, EvaluatedValue, FalseLeaf, TrueLeaf} -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.crypto.CryptoFacade.SecretKeyLength import sigmastate.eval.Extensions._ import sigmastate.eval._ diff --git a/src/it/scala/org/ergoplatform/it/WalletSpec.scala b/src/it/scala/org/ergoplatform/it/WalletSpec.scala index a3c6b4012a..3d22140ea0 100644 --- a/src/it/scala/org/ergoplatform/it/WalletSpec.scala +++ b/src/it/scala/org/ergoplatform/it/WalletSpec.scala @@ -20,6 +20,7 @@ import org.ergoplatform.{ErgoBox, P2PKAddress} import org.scalatest.wordspec.AsyncWordSpec import scorex.util.ModifierId import scorex.util.encode.Base16 +import sigma.Colls import sigmastate.Values.{ErgoTree, TrueLeaf} import scala.concurrent.ExecutionContext diff --git a/src/main/resources/api/openapi-ai.yaml b/src/main/resources/api/openapi-ai.yaml index 392309a6d1..472b4753e6 100644 --- a/src/main/resources/api/openapi-ai.yaml +++ b/src/main/resources/api/openapi-ai.yaml @@ -1,7 +1,7 @@ openapi: "3.0.2" info: - version: "5.0.14" + version: "5.0.15" title: Ergo Node API description: Specification of Ergo Node API for ChatGPT plugin. The following endpoints supported diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 563abdb14e..d7fb5d1859 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.2" info: - version: "5.0.14" + version: "5.0.15" title: Ergo Node API description: API docs for Ergo Node. Models are shared between all Ergo products contact: @@ -243,60 +243,42 @@ components: description: Name of the token, if any IndexedErgoBox: - type: object - description: Box indexed with extra information - required: - - box - - confirmationsNum - - address - - creationTransaction - - spendingTransaction - - spendingHeight - - inclusionHeight - - spent - - globalIndex - properties: - box: - $ref: '#/components/schemas/ErgoTransactionOutput' - confirmationsNum: - description: Number of confirmations, if the box is included into the blockchain - type: integer - format: int32 - minimum: 0 - example: 147 - nullable: true - address: - $ref: '#/components/schemas/ErgoAddress' - creationTransaction: - description: Transaction which created the box - $ref: '#/components/schemas/ModifierId' - spendingTransaction: - description: Transaction which created the box - nullable: true - $ref: '#/components/schemas/ModifierId' - spendingHeight: - description: The height the box was spent at - type: integer - format: int32 - minimum: 0 - example: 147 - nullable: true - inclusionHeight: - description: The height the transaction containing the box was included in a block at - type: integer - format: int32 - minimum: 0 - example: 147 - spent: - description: A flag signalling whether the box was spent - type: boolean - example: false - globalIndex: - description: Global index of the output in the blockchain - type: integer - format: int64 - minimum: 0 - example: 83927 + allOf: + - $ref: '#/components/schemas/ErgoTransactionOutput' + - type: object + description: Box indexed with extra information + required: + - address + - spentTransactionId + - spendingHeight + - inclusionHeight + - globalIndex + properties: + address: + $ref: '#/components/schemas/ErgoAddress' + spentTransactionId: + description: Transaction which spent the box + nullable: true + $ref: '#/components/schemas/ModifierId' + spendingHeight: + description: The height the box was spent at + type: integer + format: int32 + minimum: 0 + example: 147 + nullable: true + inclusionHeight: + description: The height the transaction containing the box was included in a block at + type: integer + format: int32 + minimum: 0 + example: 147 + globalIndex: + description: Global index of the output in the blockchain + type: integer + format: int64 + minimum: 0 + example: 83927 IndexedToken: type: object @@ -2721,6 +2703,42 @@ paths: schema: $ref: '#/components/schemas/ApiError' + /blocks/headerIds: + post: + summary: Get full blocks by given header ids + operationId: getFullBlockByIds + tags: + - blocks + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + '200': + description: Full blocks corresponding to ids provided + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FullBlock' + '404': + description: No block exist for every id provided + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + /blocks/{headerId}/header: get: summary: Get the block header info by a given header id @@ -5106,6 +5124,42 @@ paths: schema: $ref: '#/components/schemas/ApiError' + /utxo/withPool/byIds: + post: + summary: Get boxes for ids provided, from UTXO or the mempool. + operationId: getBoxWithPoolByIds + tags: + - utxo + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + '200': + description: Box object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ErgoTransactionOutput' + '404': + description: No any box exists for every id provided + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + /utxo/withPool/byIdBinary/{boxId}: get: summary: Get serialized box in Base16 encoding by an identifier, considering also the mempool. @@ -5240,7 +5294,7 @@ paths: $ref: '#/components/schemas/SourceHolder' responses: '200': - description: Ergo address derived from source + description: P2SH address derived from source content: application/json: schema: @@ -5601,6 +5655,41 @@ paths: schema: $ref: '#/components/schemas/ApiError' + /scan/p2sRule: + post: + security: + - ApiKeyAuth: [api_key] + summary: Create and register a scan to track P2S address provided + operationId: scriptP2SRule + tags: + - scan + requestBody: + required: true + content: + application/json: + schema: + type: string + example: '4MQyML64GnzMxZgm' + responses: + '200': + description: Id of custom scan generated and registered + content: + application/json: + schema: + $ref: '#/components/schemas/ScanId' + '400': + description: Bad source + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + /wallet/generateCommitments: post: security: @@ -6019,6 +6108,130 @@ paths: schema: $ref: '#/components/schemas/ApiError' + /blockchain/box/byTokenId/{tokenId}: + get: + summary: Retrieve boxes by an associated token id + operationId: getBoxesByTokenId + tags: + - blockchain + parameters: + - in: path + name: tokenId + required: true + description: id of the token + schema: + $ref: '#/components/schemas/ModifierId' + - in: query + name: offset + required: false + description: amount of elements to skip from the start + schema: + type: integer + format: int32 + default: 0 + - in: query + name: limit + required: false + description: amount of elements to retrieve + schema: + type: integer + format: int32 + default: 5 + responses: + '200': + description: boxes associated with wanted token + content: + application/json: + schema: + type: object + properties: + items: + type: array + description: Array of boxes + items: + $ref: '#/components/schemas/IndexedErgoBox' + total: + type: integer + description: Total number of retreived boxes + '404': + description: No boxes found for wanted token + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + + /blockchain/box/unspent/byTokenId/{tokenId}: + get: + summary: Retrieve unspent boxes by an associated token id + operationId: getBoxesByTokenIdUnspent + tags: + - blockchain + parameters: + - in: path + name: tokenId + required: true + description: id of the token + schema: + $ref: '#/components/schemas/ModifierId' + - in: query + name: offset + required: false + description: amount of elements to skip from the start + schema: + type: integer + format: int32 + default: 0 + - in: query + name: limit + required: false + description: amount of elements to retrieve + schema: + type: integer + format: int32 + default: 5 + - in: query + name: sortDirection + required: false + description: desc = new boxes first ; asc = old boxes first + schema: + type: string + default: desc + - in: query + name: includeUnconfirmed + required: false + description: if true include unconfirmed transactions from mempool + schema: + type: boolean + default: false + responses: + '200': + description: unspent boxes associated with wanted token + content: + application/json: + schema: + type: array + description: Array of boxes + items: + $ref: '#/components/schemas/IndexedErgoBox' + '404': + description: No unspent boxes found for wanted token + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + /blockchain/box/byAddress: post: summary: Retrieve boxes by their associated address @@ -6117,6 +6330,13 @@ paths: schema: type: string default: desc + - in: query + name: includeUnconfirmed + required: false + description: if true include unconfirmed transactions from mempool + schema: + type: boolean + default: false responses: '200': description: unspent boxes associated with wanted address @@ -6272,6 +6492,13 @@ paths: schema: type: string default: desc + - in: query + name: includeUnconfirmed + required: false + description: if true include unconfirmed transactions from mempool + schema: + type: boolean + default: false responses: '200': description: unspent boxes with wanted ergotree diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 4df8fa50e2..b408c2461e 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -431,7 +431,7 @@ scorex { nodeName = "ergo-node" # Network protocol version to be sent in handshakes - appVersion = 5.0.14 + appVersion = 5.0.15 # Network agent name. May contain information about client code # stack, starting from core code-base up to the end graphical interface. diff --git a/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala b/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala index f293daa633..424c279c99 100644 --- a/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala +++ b/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala @@ -27,16 +27,17 @@ import scorex.crypto.hash.Digest import scorex.util.encode.Base16 import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.basics.CryptoConstants.EcPointType -import sigmastate.basics.DLogProtocol.{DLogProverInput, FirstDLogProverMessage, ProveDlog} -import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics._ +import sigmastate.crypto.CryptoConstants.EcPointType +import sigmastate.crypto.DLogProtocol.{DLogProverInput, FirstDLogProverMessage, ProveDlog} +import sigmastate.crypto.VerifierMessage.Challenge +import sigmastate.crypto._ import sigmastate.interpreter._ import sigmastate.serialization.OpCodes -import special.sigma.AnyValue +import sigma.AnyValue import org.ergoplatform.nodeView.state.SnapshotsInfo import org.ergoplatform.nodeView.state.UtxoState.ManifestId import org.ergoplatform.sdk.JsonCodecs +import sigmastate.eval.Extensions.ArrayOps import java.math.BigInteger import scala.util.{Failure, Success, Try} @@ -200,7 +201,14 @@ trait ApiCodecs extends JsonCodecs { } yield ErgoTransaction(ergoLikeTx) } - + implicit val sigmaLeafEncoder: Encoder[SigmaLeaf] = { + leaf => + val op = leaf.opCode.toByte.asJson + leaf match { + case dlog: ProveDlog => Map("op" -> op, "h" -> dlog.value.asJson).asJson + case dht: ProveDHTuple => Map("op" -> op, "g" -> dht.g.asJson, "h" -> dht.h.asJson, "u" -> dht.u.asJson, "v" -> dht.v.asJson).asJson + } + } implicit val sigmaBooleanEncoder: Encoder[SigmaBoolean] = { sigma => @@ -218,6 +226,16 @@ trait ApiCodecs extends JsonCodecs { } } + implicit val sigmaLeafDecoder: Decoder[SigmaLeaf] = Decoder.instance { c => + c.downField("op").as[Byte].flatMap { + case b: Byte if b == OpCodes.ProveDlogCode => + c.downField("h").as[EcPointType].map(h => ProveDlog(h)) + case _ => + //only dlog is supported for now + Left(DecodingFailure("Unsupported value", List())) + } + } + implicit val sigmaBooleanDecoder: Decoder[SigmaBoolean] = Decoder.instance { c => c.downField("op").as[Byte].flatMap { case b: Byte if b == OpCodes.ProveDlogCode => @@ -251,7 +269,7 @@ trait ApiCodecs extends JsonCodecs { implicit val firstProverMessageEncoder: Encoder[FirstProverMessage] = { case cmtDlog: FirstDLogProverMessage => Json.obj("type" -> "dlog".asJson, "a" -> cmtDlog.ecData.asJson) - case cmtDht: FirstDiffieHellmanTupleProverMessage => + case cmtDht: FirstDHTupleProverMessage => Json.obj("type" -> "dht".asJson, "a" -> cmtDht.a.asJson, "b" -> cmtDht.b.asJson) case _ => ??? } @@ -266,7 +284,7 @@ trait ApiCodecs extends JsonCodecs { for { a <- c.downField("a").as[EcPointType] b <- c.downField("b").as[EcPointType] - } yield FirstDiffieHellmanTupleProverMessage(a, b) + } yield FirstDHTupleProverMessage(a, b) case _ => Left(DecodingFailure("Unsupported sigma-protocol type value", List())) } @@ -304,20 +322,20 @@ trait ApiCodecs extends JsonCodecs { case h: String if h == "cmtWithSecret" => for { secret <- c.downField("secret").as[BigInteger] - pubkey <- c.downField("pubkey").as[SigmaBoolean] + pubkey <- c.downField("pubkey").as[SigmaLeaf] position <- c.downField("position").as[NodePosition] firstMsg <- firstProverMessageDecoder.tryDecode(c) } yield OwnCommitment(pubkey, secret, firstMsg, position) case h: String if h == "cmtReal" => for { - pubkey <- c.downField("pubkey").as[SigmaBoolean] + pubkey <- c.downField("pubkey").as[SigmaLeaf] position <- c.downField("position").as[NodePosition] firstMsg <- firstProverMessageDecoder.tryDecode(c) } yield RealCommitment(pubkey, firstMsg, position) case h: String if h == "cmtSimulated" => for { position <- c.downField("position").as[NodePosition] - pubkey <- c.downField("pubkey").as[SigmaBoolean] + pubkey <- c.downField("pubkey").as[SigmaLeaf] firstMsg <- firstProverMessageDecoder.tryDecode(c) } yield SimulatedCommitment(pubkey, firstMsg, position) case _ => @@ -334,7 +352,7 @@ trait ApiCodecs extends JsonCodecs { Json.obj( "hint" -> proofType.asJson, - "challenge" -> Base16.encode(sp.challenge).asJson, + "challenge" -> Base16.encode(sp.challenge.toArray).asJson, "pubkey" -> sp.image.asJson, "proof" -> SigSerializer.toProofBytes(sp.uncheckedTree).asJson, "position" -> sp.position.asJson @@ -346,26 +364,26 @@ trait ApiCodecs extends JsonCodecs { case h: String if h == "proofReal" => for { challenge <- c.downField("challenge").as[String] - pubkey <- c.downField("pubkey").as[SigmaBoolean] + pubkey <- c.downField("pubkey").as[SigmaLeaf] proof <- c.downField("proof").as[String] position <- c.downField("position").as[NodePosition] } yield RealSecretProof( pubkey, - Challenge @@ Base16.decode(challenge).get, + Challenge @@ Base16.decode(challenge).get.toColl, SigSerializer.parseAndComputeChallenges(pubkey, Base16.decode(proof).get)(null), position ) case h: String if h == "proofSimulated" => for { challenge <- c.downField("challenge").as[String] - pubkey <- c.downField("pubkey").as[SigmaBoolean] + pubkey <- c.downField("pubkey").as[SigmaLeaf] proof <- c.downField("proof").as[String] position <- c.downField("position").as[NodePosition] } yield SimulatedSecretProof( pubkey, - Challenge @@ Base16.decode(challenge).get, + Challenge @@ Base16.decode(challenge).get.toColl, SigSerializer.parseAndComputeChallenges(pubkey, Base16.decode(proof).get)(null), position ) diff --git a/src/main/scala/org/ergoplatform/http/api/BlockchainApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/BlockchainApiRoute.scala index 408f7a0364..e5cb090761 100644 --- a/src/main/scala/org/ergoplatform/http/api/BlockchainApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/BlockchainApiRoute.scala @@ -13,6 +13,7 @@ import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.nodeView.history.extra.ExtraIndexer.ReceivableMessages.GetSegmentTreshold import org.ergoplatform.nodeView.history.extra.ExtraIndexer.{GlobalBoxIndexKey, GlobalTxIndexKey, getIndex} import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.hashErgoTree +import org.ergoplatform.nodeView.history.extra.IndexedTokenSerializer.uniqueId import org.ergoplatform.nodeView.history.extra._ import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.settings.ErgoSettings @@ -46,6 +47,8 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting } private val sortDir: Directive[Tuple1[Direction]] = parameters("sortDirection".as(sortMarshaller) ? DESC) + private val unconfirmed: Directive[Tuple1[Boolean]] = parameters("includeUnconfirmed".as[Boolean] ? false) + /** * Total number of boxes/transactions that can be requested at once to avoid too heavy requests ([[BlocksApiRoute.MaxHeaders]]) */ @@ -72,6 +75,8 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting getTxRangeR ~ getBoxByIdR ~ getBoxByIndexR ~ + getBoxesByTokenIdR ~ + getBoxesByTokenIdUnspentR ~ getBoxesByAddressR ~ getBoxesByAddressGetRoute ~ getBoxesByAddressUnspentR ~ @@ -94,11 +99,11 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting private def getHistoryWithMempool: Future[(ErgoHistoryReader,ErgoMemPoolReader)] = (readersHolder ? GetReaders).mapTo[Readers].map(r => (r.h, r.m)) - private def getAddress(tree: ErgoTree)(history: ErgoHistoryReader): Option[IndexedErgoAddress] = { + private def getAddress(tree: ErgoTree)(history: ErgoHistoryReader): Option[IndexedErgoAddress] = history.typedExtraIndexById[IndexedErgoAddress](hashErgoTree(tree)) - } - private def getAddress(addr: ErgoAddress)(history: ErgoHistoryReader): Option[IndexedErgoAddress] = getAddress(addr.script)(history) + private def getAddress(addr: ErgoAddress)(history: ErgoHistoryReader): Option[IndexedErgoAddress] = + getAddress(addr.script)(history) private def getTxById(id: ModifierId)(history: ErgoHistoryReader): Option[IndexedErgoTransaction] = history.typedExtraIndexById[IndexedErgoTransaction](id) match { @@ -237,10 +242,10 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting validateAndGetBoxesByAddress(address, offset, limit) } - private def getBoxesByAddressUnspent(addr: ErgoAddress, offset: Int, limit: Int, sortDir: Direction): Future[Seq[IndexedErgoBox]] = - getHistory.map { history => + private def getBoxesByAddressUnspent(addr: ErgoAddress, offset: Int, limit: Int, sortDir: Direction, unconfirmed: Boolean): Future[Seq[IndexedErgoBox]] = + getHistoryWithMempool.map { case (history, mempool) => getAddress(addr)(history) match { - case Some(addr) => addr.retrieveUtxos(history, offset, limit, sortDir) + case Some(addr) => addr.retrieveUtxos(history, mempool, offset, limit, sortDir, unconfirmed) case None => Seq.empty[IndexedErgoBox] } } @@ -248,26 +253,27 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting private def validateAndGetBoxesByAddressUnspent(address: ErgoAddress, offset: Int, limit: Int, - dir: Direction): Route = { + dir: Direction, + unconfirmed: Boolean): Route = { if (limit > MaxItems) { BadRequest(s"No more than $MaxItems boxes can be requested") } else if (dir == SortDirection.INVALID) { BadRequest("Invalid parameter for sort direction, valid values are \"ASC\" and \"DESC\"") } else { - ApiResponse(getBoxesByAddressUnspent(address, offset, limit, dir)) + ApiResponse(getBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed)) } } private def getBoxesByAddressUnspentR: Route = - (post & pathPrefix("box" / "unspent" / "byAddress") & ergoAddress & paging & sortDir) { - (address, offset, limit, dir) => - validateAndGetBoxesByAddressUnspent(address, offset, limit, dir) + (post & pathPrefix("box" / "unspent" / "byAddress") & ergoAddress & paging & sortDir & unconfirmed) { + (address, offset, limit, dir, unconfirmed) => + validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed) } private def getBoxesByAddressUnspentGetRoute: Route = - (pathPrefix("box" / "unspent" / "byAddress") & get & addressPass & paging & sortDir) { - (address, offset, limit, dir) => - validateAndGetBoxesByAddressUnspent(address, offset, limit, dir) + (pathPrefix("box" / "unspent" / "byAddress") & get & addressPass & paging & sortDir & unconfirmed) { + (address, offset, limit, dir, unconfirmed) => + validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed) } private def getBoxRange(offset: Int, limit: Int): Future[Seq[ModifierId]] = @@ -304,27 +310,27 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting } } - private def getBoxesByErgoTreeUnspent(tree: ErgoTree, offset: Int, limit: Int, sortDir: Direction): Future[Seq[IndexedErgoBox]] = - getHistory.map { history => + private def getBoxesByErgoTreeUnspent(tree: ErgoTree, offset: Int, limit: Int, sortDir: Direction, unconfirmed: Boolean): Future[Seq[IndexedErgoBox]] = + getHistoryWithMempool.map { case (history, mempool) => getAddress(tree)(history) match { - case Some(iEa) => iEa.retrieveUtxos(history, offset, limit, sortDir) - case None => Seq.empty[IndexedErgoBox] + case Some(addr) => addr.retrieveUtxos(history, mempool, offset, limit, sortDir, unconfirmed) + case None => Seq.empty[IndexedErgoBox] } } - private def getBoxesByErgoTreeUnspentR: Route = (post & pathPrefix("box" / "unspent" / "byErgoTree") & ergoTree & paging & sortDir) { (tree, offset, limit, dir) => + private def getBoxesByErgoTreeUnspentR: Route = (post & pathPrefix("box" / "unspent" / "byErgoTree") & ergoTree & paging & sortDir & unconfirmed) { (tree, offset, limit, dir, unconfirmed) => if(limit > MaxItems) { BadRequest(s"No more than $MaxItems boxes can be requested") }else if (dir == SortDirection.INVALID) { - BadRequest("Invalid parameter for sort direction, valid values are \"ASC\" and \"DESC\"") + BadRequest("Invalid parameter for sort direction, valid values are 'ASC' and 'DESC'") }else { - ApiResponse(getBoxesByErgoTreeUnspent(tree, offset, limit, dir)) + ApiResponse(getBoxesByErgoTreeUnspent(tree, offset, limit, dir, unconfirmed)) } } private def getTokenInfoById(id: ModifierId): Future[Option[IndexedToken]] = { getHistory.map { history => - history.typedExtraIndexById[IndexedToken](IndexedTokenSerializer.uniqueId(id)) + history.typedExtraIndexById[IndexedToken](uniqueId(id)) } } @@ -332,6 +338,36 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting ApiResponse(getTokenInfoById(id)) } + private def getBoxesByTokenId(id: ModifierId, offset: Int, limit: Int): Future[(Seq[IndexedErgoBox],Long)] = + getHistory.map { history => + history.typedExtraIndexById[IndexedToken](uniqueId(id)) match { + case Some(token) => (token.retrieveBoxes(history, offset, limit), token.boxCount) + case None => (Seq.empty[IndexedErgoBox], 0L) + } + } + + private def getBoxesByTokenIdR: Route = (get & pathPrefix("box" / "byTokenId") & modifierId & paging) { (id, offset, limit) => + ApiResponse(getBoxesByTokenId(id, offset, limit)) + } + + private def getBoxesByTokenIdUnspent(id: ModifierId, offset: Int, limit: Int, sortDir: Direction, unconfirmed: Boolean): Future[Seq[IndexedErgoBox]] = + getHistoryWithMempool.map { case (history, mempool) => + history.typedExtraIndexById[IndexedToken](uniqueId(id)) match { + case Some(token) => token.retrieveUtxos(history, mempool, offset, limit, sortDir, unconfirmed) + case None => Seq.empty[IndexedErgoBox] + } + } + + private def getBoxesByTokenIdUnspentR: Route = (get & pathPrefix("box" / "unspent" / "byTokenId") & modifierId & paging & sortDir & unconfirmed) { (id, offset, limit, dir, unconfirmed) => + if (limit > MaxItems) { + BadRequest(s"No more than $MaxItems boxes can be requested") + } else if (dir == SortDirection.INVALID) { + BadRequest("Invalid parameter for sort direction, valid values are 'ASC' and 'DESC'") + } else { + ApiResponse(getBoxesByTokenIdUnspent(id, offset, limit, dir, unconfirmed)) + } + } + private def getUnconfirmedForAddress(address: ErgoAddress)(mempool: ErgoMemPoolReader): BalanceInfo = { val bal: BalanceInfo = BalanceInfo() mempool.getAll.map(_.transaction).foreach(tx => { @@ -346,9 +382,9 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting getHistoryWithMempool.map { case (history, mempool) => getAddress(address)(history) match { case Some(addr) => - (addr.balanceInfo.get.retreiveAdditionalTokenInfo(history), getUnconfirmedForAddress(address)(mempool).retreiveAdditionalTokenInfo(history)) + (addr.balanceInfo.get.retrieveAdditionalTokenInfo(history), getUnconfirmedForAddress(address)(mempool).retrieveAdditionalTokenInfo(history)) case None => - (BalanceInfo(), getUnconfirmedForAddress(address)(mempool).retreiveAdditionalTokenInfo(history)) + (BalanceInfo(), getUnconfirmedForAddress(address)(mempool).retrieveAdditionalTokenInfo(history)) } } } diff --git a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala index 6a1e9160e8..ebf82586d8 100644 --- a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala @@ -38,6 +38,7 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo getChainSliceR ~ getBlockIdsAtHeightR ~ getBlockHeaderByHeaderIdR ~ + getFullBlockByHeaderIdsR ~ getBlockTransactionsByHeaderIdR ~ getProofForTxR ~ getFullBlockByHeaderIdR ~ @@ -69,6 +70,11 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo history.typedModifierById[Header](headerId).flatMap(history.getFullBlock) } + private def getFullBlockByHeaderIds(headerIds: Seq[ModifierId]): Future[Seq[ErgoFullBlock]] = + getHistory.map { history => + headerIds.flatMap(headerId => history.typedModifierById[Header](headerId).flatMap(history.getFullBlock)) + } + private def getModifierById(modifierId: ModifierId): Future[Option[BlockSection]] = getHistory.map(_.modifierById(modifierId)) @@ -177,4 +183,8 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo ApiResponse(getFullBlockByHeaderId(id)) } + def getFullBlockByHeaderIdsR: Route = (post & path("headerIds") & modifierIds) { ids => + ApiResponse(getFullBlockByHeaderIds(ids)) + } + } diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala index 498e73fa91..bc0ae49191 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala @@ -9,7 +9,7 @@ import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} import org.ergoplatform.settings.{Algos, ErgoSettings} import scorex.core.api.http.{ApiError, ApiRoute} -import scorex.util.{ModifierId, bytesToId} +import scorex.util.{bytesToId, ModifierId} import akka.pattern.ask import io.circe.syntax.EncoderOps import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction @@ -20,7 +20,7 @@ import sigmastate.Values.ErgoTree import sigmastate.serialization.ErgoTreeSerializer import scala.concurrent.{ExecutionContextExecutor, Future} -import scala.util.{Success, Try} +import scala.util.{Failure, Success, Try} trait ErgoBaseApiRoute extends ApiRoute with ApiCodecs { @@ -28,13 +28,32 @@ trait ErgoBaseApiRoute extends ApiRoute with ApiCodecs { val modifierId: Directive1[ModifierId] = pathPrefix(Segment).flatMap(handleModifierId) + val modifierIds: Directive1[Seq[ModifierId]] = + entity(as[Seq[String]]).flatMap(handleModifierIds) + val modifierIdGet: Directive1[ModifierId] = parameters("id".as[String]) .flatMap(handleModifierId) private def handleModifierId(value: String): Directive1[ModifierId] = { Algos.decode(value) match { case Success(bytes) => provide(bytesToId(bytes)) - case _ => reject(ValidationRejection("Wrong modifierId format")) + case _ => reject(ValidationRejection("Wrong modifierId format")) + } + } + + private def handleModifierIds(values: Seq[String]): Directive1[Seq[ModifierId]] = { + val acc = collection.mutable.Buffer.empty[ModifierId] + val err = collection.mutable.Buffer.empty[String] + for (value <- values) { + Algos.decode(value) match { + case Success(bytes) => acc += bytesToId(bytes) + case Failure(e) => err += e.getMessage + } + } + if (err.nonEmpty) { + reject(ValidationRejection(s"Wrong modifierId format for: ${err.mkString(",")}")) + } else { + provide(acc) } } @@ -49,13 +68,16 @@ trait ErgoBaseApiRoute extends ApiRoute with ApiCodecs { private def handleErgoTree(value: String): Directive1[ErgoTree] = { Base16.decode(fromJsonOrPlain(value)) match { - case Success(bytes) => provide(ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes)) + case Success(bytes) => + provide(ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes)) case _ => reject(ValidationRejection("Invalid hex data")) } } - private def getStateAndPool(readersHolder: ActorRef): Future[(ErgoStateReader, ErgoMemPoolReader)] = { + private def getStateAndPool( + readersHolder: ActorRef + ): Future[(ErgoStateReader, ErgoMemPoolReader)] = { (readersHolder ? GetReaders).mapTo[Readers].map { rs => (rs.s, rs.m) } @@ -65,14 +87,18 @@ trait ErgoBaseApiRoute extends ApiRoute with ApiCodecs { * Send local transaction to ErgoNodeViewHolder * @return Transaction Id with status OK(200), or BadRequest(400) */ - protected def sendLocalTransactionRoute(nodeViewActorRef: ActorRef, unconfirmedTx: UnconfirmedTransaction): Route = { + protected def sendLocalTransactionRoute( + nodeViewActorRef: ActorRef, + unconfirmedTx: UnconfirmedTransaction + ): Route = { val resultFuture = (nodeViewActorRef ? LocallyGeneratedTransaction(unconfirmedTx)) .mapTo[ProcessingOutcome] .flatMap { case _: Accepted => Future.successful(unconfirmedTx.transaction.id) - case _: DoubleSpendingLoser => Future.failed(new IllegalArgumentException("Double spending attempt")) - case d: Declined => Future.failed(d.e) + case _: DoubleSpendingLoser => + Future.failed(new IllegalArgumentException("Double spending attempt")) + case d: Declined => Future.failed(d.e) case i: Invalidated => Future.failed(i.e) } completeOrRecoverWith(resultFuture) { ex => @@ -87,21 +113,26 @@ trait ErgoBaseApiRoute extends ApiRoute with ApiCodecs { * Used in /transactions (POST /transactions and /transactions/check methods) and /wallet (/wallet/payment/send * and /wallet/transaction/send) API methods to check submitted or generated transaction */ - protected def verifyTransaction(tx: ErgoTransaction, - readersHolder: ActorRef, - ergoSettings: ErgoSettings): Future[Try[UnconfirmedTransaction]] = { + protected def verifyTransaction( + tx: ErgoTransaction, + readersHolder: ActorRef, + ergoSettings: ErgoSettings + ): Future[Try[UnconfirmedTransaction]] = { val now: Long = System.currentTimeMillis() - val bytes = Some(tx.bytes) + val bytes = Some(tx.bytes) getStateAndPool(readersHolder) .map { case (utxo: UtxoStateReader, mp: ErgoMemPoolReader) => val maxTxCost = ergoSettings.nodeSettings.maxTransactionCost + val validationContext = utxo.stateContext.simplifiedUpcoming() utxo.withMempool(mp) - .validateWithCost(tx, maxTxCost) + .validateWithCost(tx, validationContext, maxTxCost, None) .map(cost => new UnconfirmedTransaction(tx, Some(cost), now, now, bytes, source = None)) case _ => - tx.statelessValidity().map(_ => new UnconfirmedTransaction(tx, None, now, now, bytes, source = None)) + tx.statelessValidity() + .map(_ => new UnconfirmedTransaction(tx, None, now, now, bytes, source = None) + ) } } diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoPeersApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoPeersApiRoute.scala index 988b02135a..6efe66f3cf 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoPeersApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoPeersApiRoute.scala @@ -56,7 +56,7 @@ class ErgoPeersApiRoute(peerManager: ActorRef, con.peerInfo.map { peerInfo => PeerInfoResponse( address = peerInfo.peerSpec.declaredAddress.map(_.toString).getOrElse(""), - lastMessage = con.lastMessage, + lastMessage = peerInfo.lastStoredActivityTime, lastHandshake = peerInfo.lastHandshake, name = peerInfo.peerSpec.nodeName, connectionType = peerInfo.connectionType.map(_.toString), diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala index 5715e4d5aa..8684526663 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala @@ -13,7 +13,7 @@ import scorex.core.settings.RESTApiSettings import scorex.core.utils.ScorexEncoding import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import java.security.SecureRandom import scala.util.Failure diff --git a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala index 03e7d543a2..c9ca7c1b8c 100644 --- a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala @@ -13,7 +13,7 @@ import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.{ErgoAddress, ErgoTreePredef, Pay2SAddress} import scorex.core.api.http.ApiResponse import scorex.core.settings.RESTApiSettings -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import scala.concurrent.Future diff --git a/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala index a6110d1369..f99af46995 100644 --- a/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala @@ -5,7 +5,7 @@ import akka.http.scaladsl.server.Route import io.circe.Encoder import org.ergoplatform._ import org.ergoplatform.nodeView.wallet._ -import org.ergoplatform.nodeView.wallet.scanning.ScanRequest +import org.ergoplatform.nodeView.wallet.scanning.{EqualsScanningPredicate, ScanRequest, ScanWalletInteraction} import org.ergoplatform.settings.ErgoSettings import scorex.core.api.http.ApiError.BadRequest import scorex.core.api.http.ApiResponse @@ -13,7 +13,10 @@ import scorex.core.settings.RESTApiSettings import scala.util.{Failure, Success} import ScanEntities._ +import org.ergoplatform.ErgoBox.R1 import org.ergoplatform.wallet.Constants.ScanId +import sigmastate.Values.ByteArrayConstant +import sigmastate.serialization.ErgoTreeSerializer /** * This class contains methods to register / deregister and list external scans, and also to serve them. @@ -40,7 +43,8 @@ case class ScanApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings) unspentR ~ spentR ~ stopTrackingR ~ - addBoxR + addBoxR ~ + p2sRuleR } def registerR: Route = (path("register") & post & entity(as[ScanRequest])) { request => @@ -91,4 +95,25 @@ case class ScanApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings) case Success(_) => ApiResponse(scanIdsBox.box.id) } } + + /** + * API method to get tracking rule corresponding to p2s address + */ + def p2sRuleR: Route = (path("p2sRule") & post & entity(as[String])) { p2sRaw => + val p2s = fromJsonOrPlain(p2sRaw) + addressEncoder.fromString(p2s) match { + case Success(p2sAddr) => + val script = p2sAddr.script + val scriptBytes = ByteArrayConstant(ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(script)) + val trackingRule = EqualsScanningPredicate(R1, scriptBytes) + val request = ScanRequest(p2s, trackingRule, Some(ScanWalletInteraction.Off), Some(true)) + withWalletOp(_.addScan(request).map(_.response)) { + case Failure(e) => BadRequest(s"Bad request $request. ${Option(e.getMessage).getOrElse(e.toString)}") + case Success(app) => ApiResponse(ScanIdWrapper(app.scanId)) + } + case Failure(e) => BadRequest(s"Can't parse $p2s. ${Option(e.getMessage).getOrElse(e.toString)}") + } + } } + + diff --git a/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala index d7a0bf8f37..908e18c00b 100644 --- a/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala @@ -17,7 +17,7 @@ import scorex.core.settings.RESTApiSettings import scorex.util.encode.Base16 import sigmastate.Values.{ByteArrayConstant, ErgoTree} import sigmastate._ -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.CompiletimeIRContext import sigmastate.interpreter.Interpreter import sigmastate.lang.{CompilerResult, SigmaCompiler} @@ -39,8 +39,8 @@ case class ScriptApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings) override val route: Route = pathPrefix("script") { toStrictEntity(10.seconds) { - // p2shAddressR ~ - p2sAddressR ~ + p2shAddressR ~ + p2sAddressR ~ addressToTreeR ~ addressToBytesR ~ executeWithContextR @@ -86,7 +86,6 @@ case class ScriptApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings) } } - //todo: temporarily switched off due to https://github.com/ergoplatform/ergo/issues/936 def p2shAddressR: Route = (path("p2shAddress") & post & source) { source => withWalletOp(_.publicKeys(0, loadMaxKeys)) { addrs => compileSource(source, keysToEnv(addrs.map(_.pubkey))).map(Pay2SHAddress.apply).fold( diff --git a/src/main/scala/org/ergoplatform/http/api/UtxoApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/UtxoApiRoute.scala index 129d0bf809..4dac6ef738 100644 --- a/src/main/scala/org/ergoplatform/http/api/UtxoApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/UtxoApiRoute.scala @@ -6,7 +6,11 @@ import akka.pattern.ask import org.ergoplatform.ErgoBox import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader -import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoSetSnapshotPersistence, UtxoStateReader} +import org.ergoplatform.nodeView.state.{ + ErgoStateReader, + UtxoSetSnapshotPersistence, + UtxoStateReader +} import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import scorex.core.api.http.ApiResponse import scorex.core.settings.RESTApiSettings @@ -15,16 +19,19 @@ import scorex.util.encode.Base16 import scala.concurrent.Future -case class UtxoApiRoute(readersHolder: ActorRef, override val settings: RESTApiSettings) - (implicit val context: ActorRefFactory) extends ErgoBaseApiRoute with ApiCodecs { +case class UtxoApiRoute(readersHolder: ActorRef, override val settings: RESTApiSettings)( + implicit val context: ActorRefFactory +) extends ErgoBaseApiRoute + with ApiCodecs { - private def getState: Future[ErgoStateReader] = (readersHolder ? GetReaders).mapTo[Readers].map(_.s) + private def getState: Future[ErgoStateReader] = + (readersHolder ? GetReaders).mapTo[Readers].map(_.s) private def getStateAndPool: Future[(ErgoStateReader, ErgoMemPoolReader)] = (readersHolder ? GetReaders).mapTo[Readers].map(rs => (rs.s, rs.m)) override val route: Route = pathPrefix("utxo") { - byId ~ serializedById ~ genesis ~ withPoolById ~ withPoolSerializedById ~ getBoxesBinaryProof ~ getSnapshotsInfo + byId ~ serializedById ~ genesis ~ withPoolById ~ withPoolByIds ~ withPoolSerializedById ~ getBoxesBinaryProof ~ getSnapshotsInfo } def withPoolById: Route = (get & path("withPool" / "byId" / Segment)) { id => @@ -35,18 +42,28 @@ case class UtxoApiRoute(readersHolder: ActorRef, override val settings: RESTApiS }) } - def withPoolSerializedById: Route = (get & path("withPool" / "byIdBinary" / Segment)) { id => - ApiResponse( - getStateAndPool.map { + def withPoolByIds: Route = + (post & path("withPool" / "byIds") & entity(as[Seq[String]])) { ids => + ApiResponse(getStateAndPool.map { case (usr: UtxoStateReader, mp) => - usr.withMempool(mp).boxById(ADKey @@ Base16.decode(id).get).map { box => - val bytes = ErgoBoxSerializer.toBytes(box) - val boxBytes = Base16.encode(bytes) - Map("boxId" -> id, "bytes" -> boxBytes) - } - case _ => None - } - ) + ids.flatMap(id => usr.withMempool(mp).boxById(ADKey @@ Base16.decode(id).get)) + case _ => Seq.empty + }) + } + + def withPoolSerializedById: Route = (get & path("withPool" / "byIdBinary" / Segment)) { + id => + ApiResponse( + getStateAndPool.map { + case (usr: UtxoStateReader, mp) => + usr.withMempool(mp).boxById(ADKey @@ Base16.decode(id).get).map { box => + val bytes = ErgoBoxSerializer.toBytes(box) + val boxBytes = Base16.encode(bytes) + Map("boxId" -> id, "bytes" -> boxBytes) + } + case _ => None + } + ) } def byId: Route = (get & path("byId" / Segment)) { id => @@ -62,7 +79,7 @@ case class UtxoApiRoute(readersHolder: ActorRef, override val settings: RESTApiS getState.map { case usr: UtxoStateReader => usr.boxById(ADKey @@ Base16.decode(id).get).map { box => - val bytes = ErgoBoxSerializer.toBytes(box) + val bytes = ErgoBoxSerializer.toBytes(box) val boxBytes = Base16.encode(bytes) Map("boxId" -> id, "bytes" -> boxBytes) } @@ -75,13 +92,14 @@ case class UtxoApiRoute(readersHolder: ActorRef, override val settings: RESTApiS ApiResponse(getState.map(_.genesisBoxes)) } - def getBoxesBinaryProof: Route = (post & path("getBoxesBinaryProof") & entity(as[Seq[ErgoBox.BoxId]])) { boxes => - ApiResponse(getState.map { - case usr: UtxoStateReader => - Some(Base16.encode(usr.generateBatchProofForBoxes(boxes))) - case _ => None - }) - } + def getBoxesBinaryProof: Route = + (post & path("getBoxesBinaryProof") & entity(as[Seq[ErgoBox.BoxId]])) { boxes => + ApiResponse(getState.map { + case usr: UtxoStateReader => + Some(Base16.encode(usr.generateBatchProofForBoxes(boxes))) + case _ => None + }) + } /** * Handler for /utxo/getSnapshotsInfo API call which is providing list of diff --git a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala index 01232ad607..0db0c9fce8 100644 --- a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala +++ b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala @@ -86,7 +86,8 @@ class CleanupWorker(nodeViewHolderRef: ActorRef, ): (mutable.ArrayBuilder[UnconfirmedTransaction], mutable.ArrayBuilder[ModifierId]) = { txs match { case head :: tail if costAcc < CostLimit => - state.validateWithCost(head.transaction, nodeSettings.maxTransactionCost) match { + val validationContext = state.stateContext.simplifiedUpcoming() + state.validateWithCost(head.transaction, validationContext, nodeSettings.maxTransactionCost, None) match { case Success(txCost) => val updTx = head.withCost(txCost) validationLoop(tail, validated += updTx, invalidated, txCost + costAcc) diff --git a/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 858bc51d7b..ad5af554a6 100644 --- a/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -15,7 +15,7 @@ import org.ergoplatform.nodeView.mempool.TransactionMembershipProof import scorex.crypto.authds.{ADDigest, SerializedAdProof} import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.{ModifierId, ScorexLogging} -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.crypto.CryptoFacade import scala.annotation.tailrec diff --git a/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala b/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala index 13917b06a7..adeff25edc 100644 --- a/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala +++ b/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala @@ -8,8 +8,8 @@ import org.ergoplatform.modifiers.history.header.Header.Version import org.ergoplatform.settings.Algos import scorex.core.serialization.ErgoSerializer import scorex.util.serialization.{Reader, Writer} -import sigmastate.basics.CryptoConstants -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants +import sigmastate.crypto.CryptoConstants.EcPointType /** * Solution for an Autolykos PoW puzzle. diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 61fee2f123..17ef8af2a9 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -26,13 +26,13 @@ import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging} -import sigmastate.SType.ErgoBoxRType -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.ErgoBoxRType +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.crypto.CryptoFacade import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.interpreter.ProverResult -import special.collection.Coll +import sigma.{Coll, Colls} import scala.annotation.tailrec import scala.concurrent.duration._ @@ -819,7 +819,7 @@ object CandidateGenerator extends ScorexLogging { // check validity and calculate transaction cost stateWithTxs.validateWithCost( tx, - Some(upcomingContext), + upcomingContext, maxBlockCost, Some(verifier) ) match { diff --git a/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala b/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala index f318a4f426..732bf25805 100644 --- a/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala +++ b/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala @@ -3,7 +3,7 @@ package org.ergoplatform.mining import org.ergoplatform.modifiers.history.header.Header import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants.EcPointType import scala.util.{Random, Success, Try} diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala index 887845cd20..4379ebd26b 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala @@ -10,7 +10,7 @@ import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.GetDataFromCurrentView import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages.FullBlockApplied import scorex.util.ScorexLogging -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} import scala.concurrent.duration._ import scala.util.{Failure, Success} diff --git a/src/main/scala/org/ergoplatform/mining/WorkMessage.scala b/src/main/scala/org/ergoplatform/mining/WorkMessage.scala index 19d758d961..3c815d44fc 100644 --- a/src/main/scala/org/ergoplatform/mining/WorkMessage.scala +++ b/src/main/scala/org/ergoplatform/mining/WorkMessage.scala @@ -4,7 +4,7 @@ import io.circe.syntax._ import io.circe.{Encoder, Json} import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.nodeView.history.ErgoHistory.Height -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog /** diff --git a/src/main/scala/org/ergoplatform/mining/mining.scala b/src/main/scala/org/ergoplatform/mining/mining.scala index 3fc4be9f3e..526d2eeba2 100644 --- a/src/main/scala/org/ergoplatform/mining/mining.scala +++ b/src/main/scala/org/ergoplatform/mining/mining.scala @@ -2,9 +2,9 @@ package org.ergoplatform import org.bouncycastle.util.BigIntegers import scorex.crypto.hash.Blake2b256 -import sigmastate.basics.CryptoConstants.EcPointType -import sigmastate.basics.{BcDlogGroup, CryptoConstants} -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.CryptoConstants.EcPointType +import sigmastate.crypto.{BcDlogGroup, CryptoConstants} +import sigmastate.crypto.DLogProtocol.DLogProverInput import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer} package object mining { diff --git a/src/main/scala/org/ergoplatform/modifiers/history/PreHeader.scala b/src/main/scala/org/ergoplatform/modifiers/history/PreHeader.scala index 8387a14291..da183f33eb 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/PreHeader.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/PreHeader.scala @@ -6,7 +6,7 @@ import org.ergoplatform.modifiers.history.header.Header._ import org.ergoplatform.nodeView.history.ErgoHistory import org.ergoplatform.settings.Constants import scorex.util._ -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants.EcPointType import sigmastate.eval.CGroupElement import sigmastate.eval.Extensions._ @@ -35,7 +35,7 @@ case class CPreHeader(version: Version, object PreHeader { - def toSigma(preHeader: PreHeader): special.sigma.PreHeader = + def toSigma(preHeader: PreHeader): sigma.PreHeader = sigmastate.eval.CPreHeader( version = preHeader.version, parentId = preHeader.parentId.toBytes.toColl, diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala index cd15b11ae7..df31f80686 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala @@ -16,7 +16,7 @@ import scorex.core.serialization.ErgoSerializer import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 import scorex.util._ -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants.EcPointType import sigmastate.eval.Extensions._ import sigmastate.eval.{CAvlTree, CBigInt, CGroupElement, CHeader} @@ -136,7 +136,7 @@ object Header extends ApiCodecs { val Interpreter50Version: Byte = 3 - def toSigma(header: Header): special.sigma.Header = + def toSigma(header: Header): sigma.Header = CHeader( id = header.id.toBytes.toColl, version = header.version, diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index d00048f597..352a41ab70 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -34,11 +34,11 @@ import scorex.core.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} import scorex.core.network.DeliveryTracker import scorex.core.network.peer.PenaltyType -import scorex.core.transaction.state.TransactionValidation.TooHighCostError import scorex.crypto.hash.Digest32 import org.ergoplatform.nodeView.state.UtxoState.{ManifestId, SubtreeId} import org.ergoplatform.ErgoLikeContext.Height import scorex.core.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} +import scorex.core.transaction.TooHighCostError import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest import scala.annotation.tailrec @@ -1404,7 +1404,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, utx.source.foreach { peer => // no need to call deliveryTracker.setInvalid, as mempool will consider invalidated tx in contains() error match { - case TooHighCostError(_) => + case TooHighCostError(_, _) => log.info(s"Penalize spamming peer $peer for too costly transaction $id") penalizeSpammingPeer(peer) case _ => diff --git a/src/main/scala/org/ergoplatform/nodeView/history/extra/BalanceInfo.scala b/src/main/scala/org/ergoplatform/nodeView/history/extra/BalanceInfo.scala index a6002c1b0c..adbd35eecc 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/extra/BalanceInfo.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/extra/BalanceInfo.scala @@ -8,7 +8,7 @@ import scorex.core.serialization.ErgoSerializer import scorex.util.{ModifierId, ScorexLogging, bytesToId} import scorex.util.serialization.{Reader, Writer} import spire.implicits.cfor -import special.collection.Extensions._ +import sigma.Extensions._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -27,7 +27,7 @@ case class BalanceInfo() extends ScorexLogging { * @param history - database handle * @return this object */ - def retreiveAdditionalTokenInfo(history: ErgoHistoryReader): BalanceInfo = { + def retrieveAdditionalTokenInfo(history: ErgoHistoryReader): BalanceInfo = { additionalTokenInfo ++= tokens.map(token => { history.typedExtraIndexById[IndexedToken](uniqueId(token._1)) match { case Some(iT) => (token._1,(iT.name,iT.decimals)) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexer.scala b/src/main/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexer.scala index e7547aa3b8..f0b0e4e34c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexer.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexer.scala @@ -2,7 +2,7 @@ package org.ergoplatform.nodeView.history.extra import akka.actor.{Actor, ActorRef, ActorSystem, Props} import org.ergoplatform.ErgoBox.TokenId -import org.ergoplatform.{ErgoAddress, ErgoAddressEncoder, ErgoBox, Pay2SAddress} +import org.ergoplatform.{ErgoAddress, ErgoAddressEncoder, Pay2SAddress} import org.ergoplatform.modifiers.history.BlockTransactions import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction @@ -11,10 +11,12 @@ import org.ergoplatform.nodeView.history.extra.ExtraIndexer.{GlobalBoxIndexKey, import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} import org.ergoplatform.nodeView.history.extra.ExtraIndexer.ReceivableMessages.{GetSegmentTreshold, StartExtraIndexer} import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.hashErgoTree +import org.ergoplatform.nodeView.history.extra.IndexedTokenSerializer.uniqueId import org.ergoplatform.nodeView.history.storage.HistoryStorage import org.ergoplatform.settings.{Algos, CacheSettings, ChainSettings} import scorex.util.{ModifierId, ScorexLogging, bytesToId} import sigmastate.Values.ErgoTree +import sigma.Extensions._ import java.nio.ByteBuffer import scala.collection.mutable.ArrayBuffer @@ -27,34 +29,54 @@ import scala.collection.mutable */ trait ExtraIndexerBase extends ScorexLogging { - // Indexed block height + /** + * Indexed block height + */ protected var indexedHeight: Int = 0 - // Indexed transaction count + /** + * Indexed transaction count + */ protected var globalTxIndex: Long = 0L - // Indexed box count + /** + * Indexed box count + */ protected var globalBoxIndex: Long = 0L - // Last block height when buffer contents were saved to database + /** + * Last block height when buffer contents were saved to database + */ protected var lastWroteToDB: Int = 0 - // Max buffer size (determined by config) + /** + * Max buffer size (determined by config) + */ protected val saveLimit: Int - // Size of box and tx number containing segment addresses + /** + * Number of transaction/box numberic indexes object segments contain + */ protected implicit val segmentTreshold: Int - // Address encoder instance + /** + * Address encoder instance + */ protected implicit val addressEncoder: ErgoAddressEncoder - // Flag to signal when indexer has reached current block height + /** + * Flag to signal when indexer has reached current block height + */ protected var caughtUp: Boolean = false - // Flag to signal a rollback + /** + * Flag to signal a rollback + */ protected var rollback: Boolean = false - // Database handle + /** + * Database handle + */ protected var _history: ErgoHistory = _ protected def chainHeight: Int = _history.fullBlockHeight @@ -65,10 +87,13 @@ trait ExtraIndexerBase extends ScorexLogging { protected val general: ArrayBuffer[ExtraIndex] = ArrayBuffer.empty[ExtraIndex] protected val boxes: mutable.HashMap[ModifierId,IndexedErgoBox] = mutable.HashMap.empty[ModifierId,IndexedErgoBox] protected val trees: mutable.HashMap[ModifierId,IndexedErgoAddress] = mutable.HashMap.empty[ModifierId,IndexedErgoAddress] - protected val segments: mutable.HashMap[ModifierId,IndexedErgoAddress] = mutable.HashMap.empty[ModifierId,IndexedErgoAddress] + protected val tokens: mutable.HashMap[ModifierId,IndexedToken] = mutable.HashMap.empty[ModifierId,IndexedToken] + protected val segments: mutable.HashMap[ModifierId,Segment[_]] = mutable.HashMap.empty[ModifierId,Segment[_]] - // input tokens in a tx - protected val tokens: ArrayBuffer[(TokenId, Long)] = ArrayBuffer.empty[(TokenId, Long)] + /** + * Input tokens in a transaction + */ + private val inputTokens: ArrayBuffer[(TokenId, Long)] = ArrayBuffer.empty[(TokenId, Long)] /** * Spend an IndexedErgoBox from buffer or database. Also record tokens for later use in balance tracking logic. @@ -80,13 +105,13 @@ trait ExtraIndexerBase extends ScorexLogging { */ private def findAndSpendBox(id: ModifierId, txId: ModifierId, height: Int): Boolean = { boxes.get(id).map(box => { - tokens ++= box.asSpent(txId, height).box.additionalTokens.toArray + inputTokens ++= box.asSpent(txId, height).box.additionalTokens.toArray return true }) history.typedExtraIndexById[IndexedErgoBox](id) match { // box not found in last saveLimit modifiers case Some(x) => // box found in DB, update boxes.put(id, x.asSpent(txId, height)) - tokens ++= x.box.additionalTokens.toArray + inputTokens ++= x.box.additionalTokens.toArray true case None => // box not found at all (this shouldn't happen) log.warn(s"Unknown box used as input: $id") @@ -98,34 +123,59 @@ trait ExtraIndexerBase extends ScorexLogging { * Add or subtract a box from an address in the buffer or in database. * * @param id - hash of the (ergotree) address - * @param spendOrReceive - ErgoBox to spend or IndexedErgoBox to receive + * @param spendOrReceive - IndexedErgoBox to receive (Right) or spend (Left) */ private def findAndUpdateTree(id: ModifierId, spendOrReceive: Either[IndexedErgoBox, IndexedErgoBox]): Unit = { - trees.get(id).map(tree => { + trees.get(id).map { tree => spendOrReceive match { case Left(iEb) => tree.addTx(globalTxIndex).spendBox(iEb, Some(history)) // spend box case Right(iEb) => tree.addTx(globalTxIndex).addBox(iEb) // receive box } return - }) + } history.typedExtraIndexById[IndexedErgoAddress](id) match { // address not found in last saveLimit modifiers case Some(x) => spendOrReceive match { - case Left(box) => trees.put(id, x.addTx(globalTxIndex).spendBox(box, Some(history))) // spend box + case Left(iEb) => trees.put(id, x.addTx(globalTxIndex).spendBox(iEb, Some(history))) // spend box case Right(iEb) => trees.put(id, x.addTx(globalTxIndex).addBox(iEb)) // receive box } case None => // address not found at all spendOrReceive match { case Left(iEb) => log.warn(s"Unknown address spent box ${bytesToId(iEb.box.id)}") // spend box should never happen by an unknown address - case Right(iEb) => trees.put(id, IndexedErgoAddress(id, ArrayBuffer(globalTxIndex), ArrayBuffer.empty[Long], Some(BalanceInfo())).addBox(iEb)) // receive box + case Right(iEb) => trees.put(id, IndexedErgoAddress(id).initBalance.addTx(globalTxIndex).addBox(iEb)) // receive box, new address + } + } + } + + /** + * Add or subtract a box from a token in the buffer or in database. + * + * @param id - token id + * @param spendOrReceive - IndexedErgoBox to receive (Right) or spend (Left) + */ + private def findAndUpdateToken(id: ModifierId, spendOrReceive: Either[IndexedErgoBox, IndexedErgoBox]): Unit = { + tokens.get(id).map { token => + spendOrReceive match { + case Left(iEb) => token.spendBox(iEb, Some(history)) // spend box + case Right(iEb) => token.addBox(iEb) // receive box + } + return + } + history.typedExtraIndexById[IndexedToken](uniqueId(id)) match { // token not found in last saveLimit modifiers + case Some(x) => + spendOrReceive match { + case Left(iEb) => tokens.put(id, x.spendBox(iEb, Some(history))) // spend box + case Right(iEb) => tokens.put(id, x.addBox(iEb)) // receive box } + case None => // token not found at all + log.warn(s"Unknown token $id") // spend box should never happen by an unknown token } } /** * @return number of indexes in all buffers */ - private def modCount: Int = general.length + boxes.size + trees.size + private def modCount: Int = general.length + boxes.size + trees.size + tokens.size /** * Write buffered indexes to database and clear buffers. @@ -136,19 +186,29 @@ trait ExtraIndexerBase extends ScorexLogging { // perform segmentation on big addresses and save their internal segment buffer trees.values.foreach { tree => - if(tree.segments.nonEmpty) { - tree.segments.foreach(seg => segments.put(seg.id, seg)) - tree.segments.clear() + if(tree.buffer.nonEmpty) { + tree.buffer.values.foreach(seg => segments.put(seg.id, seg)) + tree.buffer.clear() } if(tree.txs.length > segmentTreshold || tree.boxes.length > segmentTreshold) tree.splitToSegments.foreach(seg => segments.put(seg.id, seg)) } + // perform segmentation on big tokens and save their internal segment buffer + tokens.values.foreach { token => + if(token.buffer.nonEmpty) { + token.buffer.values.foreach(seg => segments.put(seg.id, seg)) + token.buffer.clear() + } + if(token.boxes.length > segmentTreshold) + token.splitToSegments.foreach(seg => segments.put(seg.id, seg)) + } + // insert modifiers and progress info to db historyStorage.insertExtra(Array((IndexedHeightKey, ByteBuffer.allocate(4).putInt(indexedHeight).array), (GlobalTxIndexKey, ByteBuffer.allocate(8).putLong(globalTxIndex).array), (GlobalBoxIndexKey,ByteBuffer.allocate(8).putLong(globalBoxIndex).array)), - (((general ++= boxes.values) ++= trees.values) ++= segments.values).toArray) + ((((general ++= boxes.values) ++= trees.values) ++= tokens.values) ++= segments.values).toArray) if (writeLog) log.info(s"Processed ${trees.size} ErgoTrees with ${boxes.size} boxes and inserted them to database in ${System.currentTimeMillis - start}ms") @@ -157,6 +217,7 @@ trait ExtraIndexerBase extends ScorexLogging { general.clear() boxes.clear() trees.clear() + tokens.clear() segments.clear() lastWroteToDB = indexedHeight @@ -182,7 +243,7 @@ trait ExtraIndexerBase extends ScorexLogging { val tx: ErgoTransaction = bt.txs(n) val inputs: Array[Long] = Array.ofDim[Long](tx.inputs.length) - tokens.clear() + inputTokens.clear() //process transaction inputs if (height != 1) { //only after 1st block (skip genesis box) @@ -191,6 +252,9 @@ trait ExtraIndexerBase extends ScorexLogging { if (findAndSpendBox(boxId, tx.id, height)) { // spend box and add tx val iEb = boxes(boxId) findAndUpdateTree(hashErgoTree(iEb.box.ergoTree), Left(iEb)) + cfor(0)(_ < iEb.box.additionalTokens.length, _ + 1) { j => + findAndUpdateToken(iEb.box.additionalTokens(j)._1.toModifierId, Left(iEb)) + } inputs(i) = iEb.globalIndex } } @@ -198,19 +262,26 @@ trait ExtraIndexerBase extends ScorexLogging { //process transaction outputs cfor(0)(_ < tx.outputs.size, _ + 1) { i => - val box: ErgoBox = tx.outputs(i) - val boxId = bytesToId(box.id) - boxes.put(boxId, new IndexedErgoBox(height, None, None, box, globalBoxIndex)) // box by id - general += NumericBoxIndex(globalBoxIndex, bytesToId(box.id)) // box id by global box number + val iEb: IndexedErgoBox = new IndexedErgoBox(height, None, None, tx.outputs(i), globalBoxIndex) + boxes.put(iEb.id, iEb) // box by id + general += NumericBoxIndex(globalBoxIndex, iEb.id) // box id by global box number // box by address - findAndUpdateTree(hashErgoTree(box.ergoTree), Right(boxes(boxId))) + findAndUpdateTree(hashErgoTree(iEb.box.ergoTree), Right(boxes(iEb.id))) // check if box is creating new tokens, if yes record them - cfor(0)(_ < box.additionalTokens.length, _ + 1) { j => - if (!tokens.exists(x => x._1 == box.additionalTokens(j)._1)) { - general += IndexedToken.fromBox(box, j) + cfor(0)(_ < iEb.box.additionalTokens.length, _ + 1) { j => + if (!inputTokens.exists(x => x._1 == iEb.box.additionalTokens(j)._1)) { + val token = IndexedToken.fromBox(iEb, j) + tokens.get(token.tokenId) match { + case Some(t) => // same new token created in multiple boxes -> add amounts + val newToken = IndexedToken(t.tokenId, t.boxId, t.amount + token.amount, t.name, t.description, t.decimals, t.boxes) + newToken.buffer ++= t.buffer + tokens.put(token.tokenId, newToken) + case None => tokens.put(token.tokenId, token) // new token + } } + findAndUpdateToken(iEb.box.additionalTokens(j)._1.toModifierId, Right(iEb)) } globalBoxIndex += 1 @@ -287,13 +358,22 @@ trait ExtraIndexerBase extends ScorexLogging { globalTxIndex -= 1 while(globalTxIndex > txTarget) { val tx: IndexedErgoTransaction = NumericTxIndex.getTxByNumber(history, globalTxIndex).get - tx.inputNums.map(NumericBoxIndex.getBoxByNumber(history, _).get).foreach(iEb => { // undo all spendings + tx.inputNums.map(NumericBoxIndex.getBoxByNumber(history, _).get).foreach { iEb => // undo all spendings + iEb.spendingHeightOpt = None iEb.spendingTxIdOpt = None + val address = history.typedExtraIndexById[IndexedErgoAddress](hashErgoTree(iEb.box.ergoTree)).get.addBox(iEb, record = false) address.findAndModBox(iEb.globalIndex, history) - historyStorage.insertExtra(Array.empty, Array[ExtraIndex](iEb, address) ++ address.segments) - }) + historyStorage.insertExtra(Array.empty, Array[ExtraIndex](iEb, address) ++ address.buffer.values) + + cfor(0)(_ < iEb.box.additionalTokens.length, _ + 1) { i => + history.typedExtraIndexById[IndexedToken](IndexedToken.fromBox(iEb, i).id).map { token => + token.findAndModBox(iEb.globalIndex, history) + historyStorage.insertExtra(Array.empty, Array[ExtraIndex](token) ++ token.buffer.values) + } + } + } toRemove += tx.id // tx by id toRemove += bytesToId(NumericTxIndex.indexToBytes(globalTxIndex)) // tx id by number globalTxIndex -= 1 @@ -305,10 +385,11 @@ trait ExtraIndexerBase extends ScorexLogging { while(globalBoxIndex > boxTarget) { val iEb: IndexedErgoBox = NumericBoxIndex.getBoxByNumber(history, globalBoxIndex).get cfor(0)(_ < iEb.box.additionalTokens.length, _ + 1) { i => - history.typedExtraIndexById[IndexedToken](IndexedToken.fromBox(iEb.box, i).id) match { - case Some(token) if token.boxId == iEb.id => - toRemove += token.id // token created, delete - case _ => // no token created + history.typedExtraIndexById[IndexedToken](IndexedToken.fromBox(iEb, i).id).map { token => + if(token.boxId == iEb.id) // token created, delete + toRemove += token.id + else // no token created, update + toRemove ++= token.rollback(txTarget, boxTarget, _history) } } history.typedExtraIndexById[IndexedErgoAddress](hashErgoTree(iEb.box.ergoTree)).map { address => @@ -433,7 +514,7 @@ object ExtraIndexer { /** * Current newest database schema version. Used to force extra database resync. */ - val NewestVersion: Int = 3 + val NewestVersion: Int = 4 val NewestVersionBytes: Array[Byte] = ByteBuffer.allocate(4).putInt(NewestVersion).array val IndexedHeightKey: Array[Byte] = Algos.hash("indexed height") diff --git a/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedErgoAddress.scala b/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedErgoAddress.scala index 0965922603..f7408a497a 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedErgoAddress.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedErgoAddress.scala @@ -1,183 +1,38 @@ package org.ergoplatform.nodeView.history.extra -import org.ergoplatform.ErgoAddressEncoder -import org.ergoplatform.http.api.SortDirection.{ASC, DESC, Direction} +import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} import org.ergoplatform.nodeView.history.extra.ExtraIndexer.{ExtraIndexTypeId, fastIdToBytes} -import org.ergoplatform.nodeView.history.extra.IndexedErgoAddress.{getBoxes, getFromSegments, getTxs} -import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.{boxSegmentId, txSegmentId} +import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.hashErgoTree import org.ergoplatform.settings.Algos import scorex.core.serialization.ErgoSerializer -import scorex.util.{ModifierId, ScorexLogging, bytesToId} +import scorex.util.{ModifierId, bytesToId} import scorex.util.serialization.{Reader, Writer} import sigmastate.Values.ErgoTree -import spire.ClassTag import scala.collection.mutable.ArrayBuffer -import spire.syntax.all.cfor - -import java.lang.Math.abs /** * An index of an address (ErgoTree) - * @param treeHash - hash of the corresponding ErgoTree - * @param txs - list of numberic transaction indexes associated with this address - * @param boxes - list of numberic box indexes associated with this address, negative values indicate the box is spent - * @param balanceInfo - balance information (Optional because fragments do not contain it) + * @param treeHash - hash of the corresponding ErgoTree + * @param txs - list of numberic transaction indexes + * @param boxes - list of numberic box indexes, negative values indicate the box is spent */ case class IndexedErgoAddress(treeHash: ModifierId, - txs: ArrayBuffer[Long], - boxes: ArrayBuffer[Long], - balanceInfo: Option[BalanceInfo]) extends ExtraIndex with ScorexLogging { + override val txs: ArrayBuffer[Long] = new ArrayBuffer[Long], + override val boxes: ArrayBuffer[Long] = new ArrayBuffer[Long]) + extends Segment[IndexedErgoAddress](treeHash, id => IndexedErgoAddress(id), txs, boxes) with ExtraIndex { override lazy val id: ModifierId = treeHash - override def serializedId: Array[Byte] = fastIdToBytes(id) - - // Internal segment buffer used when spending boxes - private[extra] val segments: ArrayBuffer[IndexedErgoAddress] = ArrayBuffer.empty[IndexedErgoAddress] - - private[extra] var boxSegmentCount: Int = 0 - private[extra] var txSegmentCount: Int = 0 - - /** - * @return total number of transactions associated with this address - */ - def txCount(implicit segmentTreshold: Int): Long = segmentTreshold * txSegmentCount + txs.length - - /** - * @return total number of boxes associated with this address - */ - def boxCount(implicit segmentTreshold: Int): Long = segmentTreshold * boxSegmentCount + boxes.length + override def serializedId: Array[Byte] = fastIdToBytes(treeHash) /** - * Copied from [[java.util.Arrays.binarySearch]] - */ - private def binarySearch(a: ArrayBuffer[Long], key: Long): Int = { - var low: Int = 0 - var high: Int = a.length - 1 - - while (low <= high) { - val mid = (low + high) >>> 1 - val midVal = abs(a(mid)) // ignore negativity + * Balance information (Optional because fragments do not contain it) + */ + var balanceInfo: Option[BalanceInfo] = None - if (midVal < key) - low = mid + 1 - else if (midVal > key) - high = mid - 1 - else - return mid // key found - } - -1 // key not found. - } - - /** - * Retrieve segment with specified id from buffer or database - * @param history - database handle to search, if segment is not found in buffer - * @param id - address segment to search for - * @return - */ - private def getSegmentFromBufferOrHistroy(history: ErgoHistoryReader, id: ModifierId): Int = { - cfor(segments.length - 1)(_ >= 0, _ - 1) { i => - if(segments(i).id.equals(id)) return i - } - segments += history.typedExtraIndexById[IndexedErgoAddress](id).get - segments.length - 1 - } - - /** - * Locate which segment the given box number is in and change its sign, meaning it spends unspent boxes and vice versa. - * @param boxNum - box number to locate - * @param history - database to retrieve segments from - */ - private[extra] def findAndModBox(boxNum: Long, history: ErgoHistoryReader): Unit = { - val boxNumAbs = abs(boxNum) - val inCurrent: Int = binarySearch(boxes, boxNumAbs) - if(inCurrent >= 0) { // box found in current box array - boxes(inCurrent) = -boxes(inCurrent) - } else { // box is in another segment, use binary search to locate - var n = 0 - var low = 0 - var high = boxSegmentCount - 1 - while(low <= high) { - val mid = (low + high) >>> 1 - n = getSegmentFromBufferOrHistroy(history, boxSegmentId(treeHash, mid)) - if(abs(segments(n).boxes.head) < boxNumAbs && - abs(segments(n).boxes.last) < boxNumAbs) - low = mid + 1 - else if(abs(segments(n).boxes.head) > boxNumAbs && - abs(segments(n).boxes.last) > boxNumAbs) - high = mid - 1 - else - low = high + 1 // break - } - val i: Int = binarySearch(segments(n).boxes, boxNumAbs) - if(i >= 0) - segments(n).boxes(i) = -segments(n).boxes(i) - else - log.warn(s"Box $boxNum not found in any segment of parent address when trying to spend") - } - } - - /** - * Get a range of the transactions associated with this address - * @param history - history to use - * @param offset - items to skip from the start - * @param limit - items to retrieve - * @return array of transactions with full bodies - */ - def retrieveTxs(history: ErgoHistoryReader, offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[IndexedErgoTransaction] = - getFromSegments(history, treeHash, offset, limit, txSegmentCount, txs, txSegmentId, _.txs, getTxs) - - /** - * Get a range of the boxes associated with this address - * @param history - history to use - * @param offset - items to skip from the start - * @param limit - items to retrieve - * @return array of boxes - */ - def retrieveBoxes(history: ErgoHistoryReader, offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[IndexedErgoBox] = - getFromSegments(history, treeHash, offset, limit, boxSegmentCount, boxes, boxSegmentId, _.boxes, getBoxes) - - /** - * Get a range of the boxes associated with this address that are NOT spent - * @param history - history to use - * @param offset - items to skip from the start - * @param limit - items to retrieve - * @param sortDir - whether to start retreival from newest box ([[DESC]]) or oldest box ([[ASC]]) - * @return array of unspent boxes - */ - def retrieveUtxos(history: ErgoHistoryReader, offset: Int, limit: Int, sortDir: Direction): Array[IndexedErgoBox] = { - val data: ArrayBuffer[IndexedErgoBox] = ArrayBuffer.empty[IndexedErgoBox] - sortDir match { - case DESC => - data ++= boxes.filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) - var segment: Int = boxSegmentCount - while (data.length < (limit + offset) && segment > 0) { - segment -= 1 - history.typedExtraIndexById[IndexedErgoAddress](boxSegmentId(treeHash, segment)).get.boxes - .filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) ++=: data - } - data.reverse.slice(offset, offset + limit).toArray - case ASC => - var segment: Int = 0 - while (data.length < (limit + offset) && segment < boxSegmentCount) { - data ++= history.typedExtraIndexById[IndexedErgoAddress](boxSegmentId(treeHash, segment)).get.boxes - .filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) - segment += 1 - } - if(data.length < (limit + offset)) - data ++= boxes.filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) - data.slice(offset, offset + limit).toArray - } - } - - /** - * Associate transaction index with this address - * @param tx - numeric transaction index - * @return this address - */ - private[extra] def addTx(tx: Long): IndexedErgoAddress = { - if(txs.lastOption.getOrElse(-1) != tx) txs += tx // check for duplicates + private[extra] def initBalance: IndexedErgoAddress = { + balanceInfo = Some(BalanceInfo()) this } @@ -187,9 +42,9 @@ case class IndexedErgoAddress(treeHash: ModifierId, * @param record - whether to add box to boxes list, used in rollbacks (true by default) * @return this address */ - private[extra] def addBox(iEb: IndexedErgoBox, record: Boolean = true): IndexedErgoAddress = { + override private[extra] def addBox(iEb: IndexedErgoBox, record: Boolean = true): IndexedErgoAddress = { if(record) boxes += iEb.globalIndex - balanceInfo.get.add(iEb.box) + balanceInfo.foreach(_.add(iEb.box)) this } @@ -199,98 +54,53 @@ case class IndexedErgoAddress(treeHash: ModifierId, * @param historyOpt - history handle to update address fragment if spent box is old * @return this address */ - private[extra] def spendBox(iEb: IndexedErgoBox, historyOpt: Option[ErgoHistoryReader] = None)(implicit ae: ErgoAddressEncoder): IndexedErgoAddress = { - balanceInfo.get.subtract(iEb.box) - - if(historyOpt.isEmpty) - return this - - findAndModBox(iEb.globalIndex, historyOpt.get) - + override private[extra] def spendBox(iEb: IndexedErgoBox, historyOpt: Option[ErgoHistoryReader] = None)(implicit ae: ErgoAddressEncoder): IndexedErgoAddress = { + if(historyOpt.isDefined) + findAndModBox(iEb.globalIndex, historyOpt.get) + balanceInfo.foreach(_.subtract(iEb.box)) this } /** - * Rollback the state of this address and of boxes associted with it - * @param txTarget - remove transaction numbers above this number - * @param boxTarget - remove box numbers above this number and revert the balance - * @param _history - history handle to update address in database - * @return modifier ids to remove - */ - private[extra] def rollback(txTarget: Long, boxTarget: Long, _history: ErgoHistory)(implicit segmentTreshold: Int): Array[ModifierId] = { - - if((txCount == 0 && boxCount == 0) || // address already rolled back - (txs.last <= txTarget && abs(boxes.last) <= boxTarget)) // no rollback needed - return Array.empty[ModifierId] - - def history: ErgoHistoryReader = _history.getReader - - val toSave: ArrayBuffer[ExtraIndex] = ArrayBuffer.empty[ExtraIndex] - val toRemove: ArrayBuffer[ModifierId] = ArrayBuffer.empty[ModifierId] + * Add transaction index + * + * @param tx - numeric transaction index + * @return this + */ + override private[extra] def addTx(tx: Long): IndexedErgoAddress = { + if (txs.lastOption.getOrElse(-1) != tx) txs += tx // check for duplicates + this + } - // filter tx numbers - do { - val tmp = txs.takeWhile(_ <= txTarget) - txs.clear() - txs ++= tmp - if(txs.isEmpty && txSegmentCount > 0) { // entire current tx set removed, retrieving more from database if possible - val id = txSegmentId(treeHash, txSegmentCount - 1) - txs ++= history.typedExtraIndexById[IndexedErgoAddress](id).get.txs - toRemove += id - txSegmentCount -= 1 - } - }while(txCount > 0 && txs.last > txTarget) + /** + * Rollback the state of segments in memory and in db + * + * @param txTarget - remove transaction numbers above this number + * @param boxTarget - remove box numbers above this number + * @param history - history handle to update segment in database + * @return modifier ids to remove + */ + override private[extra] def rollback(txTarget: Long, boxTarget: Long, history: ErgoHistory)(implicit segmentTreshold: Int): Array[ModifierId] = { - // filter box numbers - do { - val tmp = boxes.takeWhile(abs(_) <= boxTarget) - boxes.clear() - boxes ++= tmp - if(boxes.isEmpty && boxSegmentCount > 0) { // entire current box set removed, retrieving more from database if possible - val id = boxSegmentId(treeHash, boxSegmentCount - 1) - boxes ++= history.typedExtraIndexById[IndexedErgoAddress](id).get.boxes - toRemove += id - boxSegmentCount -= 1 - } - }while(boxCount > 0 && abs(boxes.last) > boxTarget) + val toRemove: ArrayBuffer[ModifierId] = rollbackState(txTarget, boxTarget, history.getReader) - if(txCount == 0 && boxCount == 0) - toRemove += this.id // address is empty after rollback, delete + if (txCount == 0 && boxCount == 0) + toRemove += treeHash // all segments empty after rollback, delete parent else - toSave += this // save the changes made to this address - - // Save changes - _history.historyStorage.insertExtra(Array.empty, toSave.toArray) + history.historyStorage.insertExtra(Array.empty, Array(this)) // save the changes made to this address toRemove.toArray } /** - * Create an array addresses each containing a "segmentTreshold" number of this address's transaction and box indexes. - * These special addresses have their ids calculated by "txSegmentId" and "boxSegmentId" respectively. - * @return array of addresses - */ - private[extra] def splitToSegments(implicit segmentTreshold: Int): Array[IndexedErgoAddress] = { - val data: Array[IndexedErgoAddress] = new Array[IndexedErgoAddress]((txs.length / segmentTreshold) + (boxes.length / segmentTreshold)) - var i: Int = 0 + * Filter mempool boxes if API call requires it + * + * @param boxes - all boxes in mempool + * @return associated boxes + */ + override private[extra] def filterMempool(boxes: Seq[ErgoBox]): Seq[ErgoBox] = + boxes.filter(box => hashErgoTree(box.ergoTree) == treeHash) - // Split txs until under segmentTreshold - while(txs.length >= segmentTreshold) { - data(i) = new IndexedErgoAddress(txSegmentId(treeHash, txSegmentCount), txs.take(segmentTreshold), ArrayBuffer.empty[Long], None) - i += 1 - txSegmentCount += 1 - txs.remove(0, segmentTreshold) - } - - // Split boxes until under segmentTreshold - while(boxes.length >= segmentTreshold) { - data(i) = new IndexedErgoAddress(boxSegmentId(treeHash, boxSegmentCount), ArrayBuffer.empty[Long], boxes.take(segmentTreshold), None) - i += 1 - boxSegmentCount += 1 - boxes.remove(0, segmentTreshold) - } - data - } } object IndexedErgoAddressSerializer extends ErgoSerializer[IndexedErgoAddress] { @@ -302,45 +112,17 @@ object IndexedErgoAddressSerializer extends ErgoSerializer[IndexedErgoAddress] { */ def hashErgoTree(tree: ErgoTree): ModifierId = bytesToId(Algos.hash(tree.bytes)) - /** - * Calculates id of an address segment containing box indexes. - * @param hash - hash of the parent addresses ErgoTree - * @param segmentNum - numberic identifier of the segment - * @return calculated ModifierId - */ - def boxSegmentId(hash: ModifierId, segmentNum: Int): ModifierId = bytesToId(Algos.hash(hash + " box segment " + segmentNum)) - - /** - * Calculates id of an address segment transaction indexes. - * @param hash - hash of the parent addresses ErgoTree - * @param segmentNum - numberic identifier of the segment - * @return calculated ModifierId - */ - def txSegmentId(hash: ModifierId, segmentNum: Int): ModifierId = bytesToId(Algos.hash(hash + " tx segment " + segmentNum)) - override def serialize(iEa: IndexedErgoAddress, w: Writer): Unit = { - w.putBytes(fastIdToBytes(iEa.treeHash)) - w.putUInt(iEa.txs.length) - cfor(0)(_ < iEa.txs.length, _ + 1) { i => w.putLong(iEa.txs(i))} - w.putUInt(iEa.boxes.length) - cfor(0)(_ < iEa.boxes.length, _ + 1) { i => w.putLong(iEa.boxes(i))} + w.putBytes(iEa.serializedId) w.putOption[BalanceInfo](iEa.balanceInfo)((ww, bI) => BalanceInfoSerializer.serialize(bI, ww)) - w.putInt(iEa.boxSegmentCount) - w.putInt(iEa.txSegmentCount) + SegmentSerializer.serialize(iEa, w) } override def parse(r: Reader): IndexedErgoAddress = { val addressHash: ModifierId = bytesToId(r.getBytes(32)) - val txnsLen: Long = r.getUInt() - val txns: ArrayBuffer[Long] = ArrayBuffer.empty[Long] - cfor(0)(_ < txnsLen, _ + 1) { _ => txns += r.getLong()} - val boxesLen: Long = r.getUInt() - val boxes: ArrayBuffer[Long] = ArrayBuffer.empty[Long] - cfor(0)(_ < boxesLen, _ + 1) { _ => boxes += r.getLong()} - val balanceInfo: Option[BalanceInfo] = r.getOption[BalanceInfo](BalanceInfoSerializer.parse(r)) - val iEa: IndexedErgoAddress = new IndexedErgoAddress(addressHash, txns, boxes, balanceInfo) - iEa.boxSegmentCount = r.getInt() - iEa.txSegmentCount = r.getInt() + val iEa: IndexedErgoAddress = new IndexedErgoAddress(addressHash) + iEa.balanceInfo = r.getOption[BalanceInfo](BalanceInfoSerializer.parse(r)) + SegmentSerializer.parse(r, iEa) iEa } } @@ -349,81 +131,4 @@ object IndexedErgoAddress { val extraIndexTypeId: ExtraIndexTypeId = 15.toByte - /** - * Calculate the segment offsets for the given range. - * @param offset - items to skip from the start - * @param limit - items to retrieve - * @return array of offsets - */ - private def getSegmentsForRange(offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[Int] = - (math.max(math.floor(offset * 1F / segmentTreshold).toInt, 1) to math.ceil((offset + limit) * 1F / segmentTreshold).toInt).toArray - - /** - * Get a range of elements from an ArrayBuffer by removing the last "offset" elements, - * then getting the last "limit" elements reversed. - * @param arr - array to get range from - * @param offset - number of items to skip from the end - * @param limit - number of items to retrieve - * @return a reversed range in "arr" ArrayBuffer - */ - private def sliceReversed(arr: ArrayBuffer[Long], offset: Int, limit: Int): ArrayBuffer[Long] = - arr.slice(arr.length - limit - offset, arr.length - offset).reverse - - /** - * Get an array of transactions with full bodies from an array of numeric transaction indexes - * @param arr - array of numeric transaction indexes to retrieve - * @param history - database handle - * @return array of transactions with full bodies - */ - private def getTxs(arr: ArrayBuffer[Long], history: ErgoHistoryReader): Array[IndexedErgoTransaction] = // sorted to match explorer - arr.map(n => NumericTxIndex.getTxByNumber(history, n).get.retrieveBody(history)).toArray.sortBy(tx => (-tx.height, tx.id)) - - /** - * Get an array of boxes from an array of numeric box indexes - * @param arr - array of numeric box indexes to retrieve - * @param history - database handle - * @return array of boxes - */ - private def getBoxes(arr: ArrayBuffer[Long], history: ErgoHistoryReader): Array[IndexedErgoBox] = - arr.map(n => NumericBoxIndex.getBoxByNumber(history, n).get).toArray - - /** - * Get a set of address segments from database containing numeric transaction or box indexes. Then actually retreive these indexes. - * @param history - database handle - * @param treeHash - hash of parent ErgoTree - * @param offset - number of items to skip from the start - * @param limit - max number of item to be returned - * @param segmentCount - number of segments of the parent address - * @param array - the indexes already in memory - * @param idOf - function to calculate segment ids, either [[txSegmentId]] or [[boxSegmentId]] - * @param arraySelector - function to select index array from retreived segments - * @param retreive - function to retreive indexes from database - * @tparam T - type of desired indexes, either [[IndexedErgoTransaction]] or [[IndexedErgoBox]] - * @return - */ - private def getFromSegments[T : ClassTag](history: ErgoHistoryReader, - treeHash: ModifierId, - offset: Int, - limit: Int, - segmentCount: Int, - array: ArrayBuffer[Long], - idOf: (ModifierId, Int) => ModifierId, - arraySelector: IndexedErgoAddress => ArrayBuffer[Long], - retreive: (ArrayBuffer[Long], ErgoHistoryReader) => Array[T]) - (implicit segmentTreshold: Int): Array[T] = { - val total: Int = segmentTreshold * segmentCount + array.length - if(offset >= total) - return Array.empty[T] // return empty array if all elements are skipped - if (offset + limit > array.length && segmentCount > 0) { - val data: ArrayBuffer[Long] = ArrayBuffer.empty[Long] - getSegmentsForRange(offset, limit).map(n => math.max(segmentCount - n, 0)).distinct.foreach { num => - arraySelector( - history.typedExtraIndexById[IndexedErgoAddress](idOf(treeHash, num)).get - ) ++=: data - } - data ++= (if (offset < array.length) array else Nil) - retreive(sliceReversed(data, offset % segmentTreshold, math.min(total - offset, limit)), history) - } else - retreive(sliceReversed(array, offset, limit), history) - } } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedToken.scala b/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedToken.scala index 19c6d18d8e..ec00019729 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedToken.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/extra/IndexedToken.scala @@ -1,36 +1,105 @@ package org.ergoplatform.nodeView.history.extra -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} import org.ergoplatform.ErgoBox.{R4, R5, R6} +import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} import org.ergoplatform.nodeView.history.extra.ExtraIndexer.{ExtraIndexTypeId, fastIdToBytes} import org.ergoplatform.nodeView.history.extra.IndexedTokenSerializer.{ByteColl, uniqueId} import org.ergoplatform.settings.Algos import scorex.core.serialization.ErgoSerializer -import scorex.util.{ByteArrayOps, ModifierId, bytesToId} +import scorex.util.{ModifierId, bytesToId} import scorex.util.serialization.{Reader, Writer} import sigmastate.Values.CollectionConstant import sigmastate.SByte -import special.collection.Extensions._ +import sigma.Extensions._ + +import scala.collection.mutable.ArrayBuffer /** * Index of a token containing creation information. * @param tokenId - id of this token - * @param boxId - id of the box that created th is token + * @param boxId - id of the box that created this token * @param amount - emission amount * @param name - name of this token (UTF-8) * @param description - description of this token (UTF-8) * @param decimals - number of decimal places + * @param boxes - list of numberic box indexes, negative values indicate the box is spent */ case class IndexedToken(tokenId: ModifierId, - boxId: ModifierId, - amount: Long, - name: String, - description: String, - decimals: Int) extends ExtraIndex { + boxId: ModifierId = ModifierId @@ "", + amount: Long = 0L, + name: String = "", + description: String = "", + decimals: Int = 0, + override val boxes: ArrayBuffer[Long] = new ArrayBuffer[Long]) + extends Segment[IndexedToken](tokenId, id => IndexedToken(id), new ArrayBuffer[Long], boxes, uniqueId) with ExtraIndex { override lazy val id: ModifierId = uniqueId(tokenId) override def serializedId: Array[Byte] = fastIdToBytes(id) + /** + * Rollback the state of segments in memory and in db + * + * @param txTarget - remove transaction numbers above this number + * @param boxTarget - remove box numbers above this number + * @param history - history handle to update segment(s) in database + * @return modifier ids to remove + */ + override private[extra] def rollback(txTarget: Long, boxTarget: Long, history: ErgoHistory)(implicit segmentTreshold: Int): Array[ModifierId] = { + + val toRemove: ArrayBuffer[ModifierId] = rollbackState(txTarget, boxTarget, history.getReader) + + if (boxCount == 0) + toRemove += id // all segments empty after rollback, delete parent + else + history.historyStorage.insertExtra(Array.empty, Array(this)) // save the changes made to this address + + toRemove.toArray + } + + /** + * Add transaction index + * + * @param tx - numeric transaction index + * @return this + */ + @deprecated("Indexed tokens do not track transactions", "") + override private[extra] def addTx(tx: Long): IndexedToken = this + + /** + * Add box index + * + * @param iEb - box to use + * @param record - whether to add box to boxes list, used in rollbacks (true by default) + * @return this + */ + override private[extra] def addBox(iEb: IndexedErgoBox, record: Boolean = true): IndexedToken = { + if(record) boxes += iEb.globalIndex + this + } + + /** + * Update segments in memory or in database by spending a box + * + * @param iEb - box to spend + * @param historyOpt - history handle to update segment in db if spent box is old + * @return this + */ + override private[extra] def spendBox(iEb: IndexedErgoBox, historyOpt: Option[ErgoHistoryReader])(implicit ae: ErgoAddressEncoder): IndexedToken = { + if(historyOpt.isDefined) + findAndModBox(iEb.globalIndex, historyOpt.get) + this + } + + /** + * Filter mempool boxes if API call requires it + * + * @param boxes - all boxes in mempool + * @return associated boxes + */ + override private[extra] def filterMempool(boxes: Seq[ErgoBox]): Seq[ErgoBox] = + boxes.filter(_.additionalTokens.exists(_._1.toModifierId == tokenId)) + } object IndexedTokenSerializer extends ErgoSerializer[IndexedToken] { @@ -47,6 +116,7 @@ object IndexedTokenSerializer extends ErgoSerializer[IndexedToken] { override def serialize(iT: IndexedToken, w: Writer): Unit = { w.putBytes(fastIdToBytes(iT.tokenId)) + w.putUByte(iT.boxId.length / 2) w.putBytes(fastIdToBytes(iT.boxId)) w.putULong(iT.amount) val name: Array[Byte] = iT.name.getBytes("UTF-8") @@ -56,18 +126,22 @@ object IndexedTokenSerializer extends ErgoSerializer[IndexedToken] { w.putUShort(description.length) w.putBytes(description) w.putInt(iT.decimals) + SegmentSerializer.serialize(iT, w) } override def parse(r: Reader): IndexedToken = { val tokenId: ModifierId = bytesToId(r.getBytes(32)) - val boxId: ModifierId = bytesToId(r.getBytes(32)) + val boxIdLen: Int = r.getUByte() + val boxId: ModifierId = bytesToId(r.getBytes(boxIdLen)) val amount: Long = r.getULong() val nameLen: Int = r.getUShort() val name: String = new String(r.getBytes(nameLen), "UTF-8") val descLen: Int = r.getUShort() val description: String = new String(r.getBytes(descLen), "UTF-8") val decimals: Int = r.getInt() - IndexedToken(tokenId, boxId, amount, name, description, decimals) + val iT = IndexedToken(tokenId, boxId, amount, name, description, decimals) + SegmentSerializer.parse(r, iT) + iT } } @@ -80,13 +154,13 @@ object IndexedToken { * Tokens can be created without setting registers or something other than token information can be in them, * so they are checked with Try-catches. * - * @param box - box to use + * @param iEb - box to use * @param tokenIndex - token index to check in [[ErgoBox.additionalTokens]] * @return token index */ - def fromBox(box: ErgoBox, tokenIndex: Int): IndexedToken = { + def fromBox(iEb: IndexedErgoBox, tokenIndex: Int): IndexedToken = { val name: String = - box.additionalRegisters.get(R4) match { + iEb.box.additionalRegisters.get(R4) match { case Some(reg) => try { new String(reg.asInstanceOf[ByteColl].value.toArray, "UTF-8") @@ -97,7 +171,7 @@ object IndexedToken { } val description: String = - box.additionalRegisters.get(R5) match { + iEb.box.additionalRegisters.get(R5) match { case Some(reg) => try { new String(reg.asInstanceOf[ByteColl].value.toArray, "UTF-8") @@ -109,7 +183,7 @@ object IndexedToken { val decimals: Int = - box.additionalRegisters.get(R6) match { + iEb.box.additionalRegisters.get(R6) match { case Some(reg) => try { new String(reg.asInstanceOf[ByteColl].value.toArray, "UTF-8").toInt @@ -124,9 +198,9 @@ object IndexedToken { case None => 0 } - IndexedToken(box.additionalTokens(tokenIndex)._1.toModifierId, - box.id.toModifierId, - box.additionalTokens(tokenIndex)._2, + IndexedToken(iEb.box.additionalTokens(tokenIndex)._1.toModifierId, + iEb.id, + iEb.box.additionalTokens(tokenIndex)._2, name, description, decimals) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/extra/Segment.scala b/src/main/scala/org/ergoplatform/nodeView/history/extra/Segment.scala new file mode 100644 index 0000000000..e1f82c7d8b --- /dev/null +++ b/src/main/scala/org/ergoplatform/nodeView/history/extra/Segment.scala @@ -0,0 +1,438 @@ +package org.ergoplatform.nodeView.history.extra + +import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} +import org.ergoplatform.http.api.SortDirection.{ASC, DESC, Direction} +import org.ergoplatform.nodeView.history.extra.ExtraIndexer.fastIdToBytes +import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} +import org.ergoplatform.nodeView.history.extra.SegmentSerializer._ +import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.sdk.JavaHelpers.Algos +import scorex.util.serialization.{Reader, Writer} +import scorex.util.{ModifierId, ScorexLogging, bytesToId} +import spire.implicits.cfor + +import scala.collection.mutable.ArrayBuffer +import java.lang.Math.abs +import scala.collection.mutable +import scala.reflect.ClassTag + +/** + * Class to manage the tracking of transactions/boxes in relation to some other object (ErgoTree/token). + * When [[ExtraIndexerBase.segmentTreshold]] number of transaction/box indexes are accumulated, new instances of the parent object are created to contain them. + * This mechanism is used to prevent excessive serialization/deserialization delays caused by objects with a lot of transaction/box indexes. + * @param parentId - identifier of parent object + * @param factory - parent object factory + * @param txs - list of numberic transaction indexes + * @param boxes - list of numberic box indexes, negative values indicate the box is spent + * @param idMod - function to apply to ids during segmentation and db lookup + * @tparam T - type of parent object + */ +abstract class Segment[T <: Segment[_] : ClassTag](val parentId: ModifierId, + val factory: ModifierId => T, + val txs: ArrayBuffer[Long], + val boxes: ArrayBuffer[Long], + val idMod: ModifierId => ModifierId = id => id) + extends ExtraIndex with ScorexLogging { + + override lazy val id: ModifierId = parentId + override def serializedId: Array[Byte] = fastIdToBytes(parentId) + + /** + * Internal segment buffer + */ + private[extra] val buffer: mutable.HashMap[ModifierId,T] = new mutable.HashMap[ModifierId,T] + + /** + * Number of segments in database containing box numbers + */ + private[extra] var boxSegmentCount: Int = 0 + + /** + * Number of segments in database containing transaction numbers + */ + private[extra] var txSegmentCount: Int = 0 + + /** + * @return total number of boxes associated with the parent object + */ + def boxCount(implicit segmentTreshold: Int): Long = segmentTreshold * boxSegmentCount + boxes.length + + /** + * @return total number of transactions associated with the parent object + */ + def txCount(implicit segmentTreshold: Int): Long = segmentTreshold * txSegmentCount + txs.length + + /** + * Locate which segment the given box number is in and change its sign, meaning it spends unspent boxes and vice versa. + * + * @param boxNum - box number to locate + * @param history - database to retrieve segments from + */ + private[extra] def findAndModBox(boxNum: Long, history: ErgoHistoryReader): Unit = { + val boxNumAbs = abs(boxNum) + val inCurrent: Int = binarySearch(boxes, boxNumAbs) + if(inCurrent >= 0) { // box found in current box array + boxes.update(inCurrent, -boxes(inCurrent)) + } else { // box is in another segment, use binary search to locate + var segmentId: ModifierId = ModifierId @@ "" + var low = 0 + var high = boxSegmentCount - 1 + while(low <= high) { + val mid = (low + high) >>> 1 + segmentId = boxSegmentId(parentId, mid) + buffer.get(segmentId).orElse(history.typedExtraIndexById[T](idMod(segmentId))).foreach { segment => + if(abs(segment.boxes.head) < boxNumAbs && + abs(segment.boxes.last) < boxNumAbs) + low = mid + 1 + else if(abs(segment.boxes.head) > boxNumAbs && + abs(segment.boxes.last) > boxNumAbs) + high = mid - 1 + else { + low = high + 1 // break + buffer.put(segmentId, segment) + } + } + } + buffer.get(segmentId) match { + case Some(segment) => + val i: Int = binarySearch(segment.boxes, boxNumAbs) + segment.boxes(i) = -segment.boxes(i) + case None => + log.warn(s"Box $boxNum not found in any segment of parent when trying to spend") + } + } + } + + /** + * Create an array of parent objects each containing [[ExtraIndexerBase.segmentTreshold]] number of transaction/box indexes. + * These objects have their ids calculated by "txSegmentId" and "boxSegmentId" respectively. + * + * @return array of parent objects + */ + private[extra] def splitToSegments(implicit segmentTreshold: Int): Array[T] = { + val data: ArrayBuffer[T] = new ArrayBuffer[T] + + // Split txs until under segmentTreshold + while(txs.length > segmentTreshold) { + data += factory(txSegmentId(parentId, txSegmentCount)) + data.last.txs ++= txs.take(segmentTreshold) + txSegmentCount += 1 + txs.remove(0, segmentTreshold) + } + + // Split boxes until under segmentTreshold + while(boxes.length > segmentTreshold) { + data += factory(boxSegmentId(parentId, boxSegmentCount)) + data.last.boxes ++= boxes.take(segmentTreshold) + boxSegmentCount += 1 + boxes.remove(0, segmentTreshold) + } + data.toArray + } + + /** + * Calculate the segment offsets for the given range. + * + * @param offset - items to skip from the start + * @param limit - items to retrieve + * @return array of offsets + */ + private def getSegmentsForRange(offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[Int] = + (math.max(math.floor(offset * 1F / segmentTreshold).toInt, 1) to math.ceil((offset + limit) * 1F / segmentTreshold).toInt).toArray + + /** + * Get a range of elements from an ArrayBuffer by removing the last "offset" elements, + * then getting the last "limit" elements reversed. + * + * @param arr - array to get range from + * @param offset - number of items to skip from the end + * @param limit - number of items to retrieve + * @return a reversed range in "arr" ArrayBuffer + */ + private def sliceReversed(arr: ArrayBuffer[Long], offset: Int, limit: Int): ArrayBuffer[Long] = + arr.slice(arr.length - limit - offset, arr.length - offset).reverse + + /** + * Get an array of transactions with full bodies from an array of numeric transaction indexes + * + * @param arr - array of numeric transaction indexes to retrieve + * @param history - database handle + * @return array of transactions with full bodies + */ + private def getTxs(arr: ArrayBuffer[Long], history: ErgoHistoryReader): Array[IndexedErgoTransaction] = // sorted to match explorer + arr.map(n => NumericTxIndex.getTxByNumber(history, n).get.retrieveBody(history)).toArray.sortBy(tx => (-tx.height, tx.id)) + + /** + * Get an array of boxes from an array of numeric box indexes + * + * @param arr - array of numeric box indexes to retrieve + * @param history - database handle + * @return array of boxes + */ + private def getBoxes(arr: ArrayBuffer[Long], history: ErgoHistoryReader): Array[IndexedErgoBox] = + arr.map(n => NumericBoxIndex.getBoxByNumber(history, n).get).toArray + + /** + * Get a set of segments from database containing numeric transaction or box indexes. Then actually retreive these indexes. + * + * @param history - database handle + * @param offset - number of items to skip from the start + * @param limit - max number of item to be returned + * @param segmentCount - number of segments of the parent address + * @param array - the indexes already in memory + * @param idOf - function to calculate segment ids, either [[txSegmentId]] or [[boxSegmentId]] + * @param arraySelector - function to select index array from retreived segments + * @param retreive - function to retreive indexes from database + * @tparam B - type of desired indexes, either [[IndexedErgoTransaction]] or [[IndexedErgoBox]] + * @return + */ + private def getFromSegments[B: ClassTag](history: ErgoHistoryReader, + offset: Int, + limit: Int, + segmentCount: Int, + array: ArrayBuffer[Long], + idOf: (ModifierId, Int) => ModifierId, + arraySelector: T => ArrayBuffer[Long], + retreive: (ArrayBuffer[Long], ErgoHistoryReader) => Array[B]) + (implicit segmentTreshold: Int): Array[B] = { + val total: Int = segmentTreshold * segmentCount + array.length + if(offset >= total) + return Array.empty[B] // return empty array if all elements are skipped + if(offset + limit > array.length && segmentCount > 0) { + val data: ArrayBuffer[Long] = ArrayBuffer.empty[Long] + getSegmentsForRange(offset, limit).map(n => math.max(segmentCount - n, 0)).distinct.foreach { num => + arraySelector( + history.typedExtraIndexById[T](idMod(idOf(parentId, num))).get + ) ++=: data + } + data ++= (if(offset < array.length) array else Nil) + retreive(sliceReversed(data, offset % segmentTreshold, math.min(total - offset, limit)), history) + } else + retreive(sliceReversed(array, offset, limit), history) + } + + /** + * Get a range of the transactions associated with the parent object + * + * @param history - history to use + * @param offset - items to skip from the start + * @param limit - items to retrieve + * @return array of transactions with full bodies + */ + def retrieveTxs(history: ErgoHistoryReader, offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[IndexedErgoTransaction] = + getFromSegments(history, offset, limit, txSegmentCount, txs, txSegmentId, _.txs, getTxs) + + /** + * Get a range of the boxes associated with the parent object + * + * @param history - history to use + * @param offset - items to skip from the start + * @param limit - items to retrieve + * @return array of boxes + */ + def retrieveBoxes(history: ErgoHistoryReader, offset: Int, limit: Int)(implicit segmentTreshold: Int): Array[IndexedErgoBox] = + getFromSegments(history, offset, limit, boxSegmentCount, boxes, boxSegmentId, _.boxes, getBoxes) + + /** + * Get a range of the boxes associated with the parent that are NOT spent + * + * @param history - history to use + * @param mempool - mempool to use, if unconfirmed is true + * @param offset - items to skip from the start + * @param limit - items to retrieve + * @param sortDir - whether to start retreival from newest box ([[DESC]]) or oldest box ([[ASC]]) + * @param unconfirmed - whether to include unconfirmed boxes + * @return array of unspent boxes + */ + def retrieveUtxos(history: ErgoHistoryReader, + mempool: ErgoMemPoolReader, + offset: Int, + limit: Int, + sortDir: Direction, + unconfirmed: Boolean): Seq[IndexedErgoBox] = { + val data: ArrayBuffer[IndexedErgoBox] = ArrayBuffer.empty[IndexedErgoBox] + val confirmedBoxes: Seq[IndexedErgoBox] = sortDir match { + case DESC => + data ++= boxes.filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) + var segment: Int = boxSegmentCount + while(data.length < (limit + offset) && segment > 0) { + segment -= 1 + history.typedExtraIndexById[T](idMod(boxSegmentId(parentId, segment))).get.boxes + .filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) ++=: data + } + data.reverse.slice(offset, offset + limit) + case ASC => + var segment: Int = 0 + while(data.length < (limit + offset) && segment < boxSegmentCount) { + data ++= history.typedExtraIndexById[T](idMod(boxSegmentId(parentId, segment))).get.boxes + .filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) + segment += 1 + } + if (data.length < (limit + offset)) + data ++= boxes.filter(_ > 0).map(n => NumericBoxIndex.getBoxByNumber(history, n).get) + data.slice(offset, offset + limit) + } + if(unconfirmed) { + val mempoolBoxes = filterMempool(mempool.getAll.flatMap(_.transaction.outputs)) + val unconfirmedBoxes = mempoolBoxes.map(new IndexedErgoBox(0, None, None, _, 0)) + sortDir match { + case DESC => unconfirmedBoxes ++ confirmedBoxes + case ASC => confirmedBoxes ++ unconfirmedBoxes + } + } else + confirmedBoxes + } + + /** + * Logic for [[Segment.rollback]] + * + * @param txTarget - remove transaction numbers above this number + * @param boxTarget - remove box numbers above this number + * @param history - history handle to update segment(s) in database + * @return modifier ids to remove + */ + protected def rollbackState(txTarget: Long, boxTarget: Long, history: ErgoHistoryReader) + (implicit segmentTreshold: Int): ArrayBuffer[ModifierId] = { + + if((txCount == 0 && boxCount == 0) || // already rolled back + (txs.lastOption.getOrElse[Long](0) <= txTarget && + abs(boxes.lastOption.getOrElse[Long](0)) <= boxTarget)) // no rollback needed + return ArrayBuffer.empty[ModifierId] + + val toRemove: ArrayBuffer[ModifierId] = ArrayBuffer.empty[ModifierId] + + // filter tx numbers + do { + val tmp = txs.takeWhile(_ <= txTarget) + txs.clear() + txs ++= tmp + if (txs.isEmpty && txSegmentCount > 0) { // entire current tx set removed, retrieving more from database if possible + val segmentId = txSegmentId(parentId, txSegmentCount - 1) + history.typedExtraIndexById[T](idMod(segmentId)).get.txs ++=: txs + toRemove += segmentId + txSegmentCount -= 1 + } + } while (txCount > 0 && txs.last > txTarget) + + // filter box numbers + do { + val tmp = boxes.takeWhile(abs(_) <= boxTarget) + boxes.clear() + boxes ++= tmp + if (boxes.isEmpty && boxSegmentCount > 0) { // entire current box set removed, retrieving more from database if possible + val segmentId = boxSegmentId(parentId, boxSegmentCount - 1) + history.typedExtraIndexById[T](idMod(segmentId)).get.boxes ++=: boxes + toRemove += segmentId + boxSegmentCount -= 1 + } + } while (boxCount > 0 && abs(boxes.last) > boxTarget) + + toRemove + } + + /** + * Rollback the state of segments in memory and in db + * + * @param txTarget - remove transaction numbers above this number + * @param boxTarget - remove box numbers above this number + * @param history - history handle to update segment(s) in database + * @return modifier ids to remove + */ + private[extra] def rollback(txTarget: Long, boxTarget: Long, history: ErgoHistory)(implicit segmentTreshold: Int): Array[ModifierId] + + /** + * Add transaction index + * + * @param tx - numeric transaction index + * @return this + */ + private[extra] def addTx(tx: Long): T + + /** + * Add box index + * + * @param iEb - box to use + * @param record - whether to add box to boxes list, used in rollbacks (true by default) + * @return this + */ + private[extra] def addBox(iEb: IndexedErgoBox, record: Boolean = true): T + + /** + * Update segments in memory or in database by spending a box + * + * @param iEb - box to spend + * @param historyOpt - history handle to update segment in db if spent box is old + * @return this + */ + private[extra] def spendBox(iEb: IndexedErgoBox, historyOpt: Option[ErgoHistoryReader] = None)(implicit ae: ErgoAddressEncoder): T + + /** + * Filter mempool boxes if API call requires it + * + * @param boxes - all boxes in mempool + * @return associated boxes + */ + private[extra] def filterMempool(boxes: Seq[ErgoBox]): Seq[ErgoBox] + +} + +object SegmentSerializer { + + /** + * Calculates the id of a segment containing box indexes. + * + * @param parentId - parent class identifier + * @param segmentNum - numberic identifier of the segment + * @return calculated ModifierId + */ + def boxSegmentId(parentId: ModifierId, segmentNum: Int): ModifierId = bytesToId(Algos.hash(parentId + " box segment " + segmentNum)) + + /** + * Calculates the id of a segment containing transaction indexes. + * + * @param parentId - parent class identifier + * @param segmentNum - numberic identifier of the segment + * @return calculated ModifierId + */ + def txSegmentId(parentId: ModifierId, segmentNum: Int): ModifierId = bytesToId(Algos.hash(parentId + " tx segment " + segmentNum)) + + /** + * Copied from [[java.util.Arrays.binarySearch]] + */ + private[extra] def binarySearch(a: ArrayBuffer[Long], key: Long): Int = { + var low: Int = 0 + var high: Int = a.length - 1 + + while (low <= high) { + val mid = (low + high) >>> 1 + val midVal = abs(a(mid)) // ignore negativity + + if (midVal < key) + low = mid + 1 + else if (midVal > key) + high = mid - 1 + else + return mid // key found + } + -1 // key not found. + } + + def serialize(s: Segment[_], w: Writer): Unit = { + w.putUInt(s.txs.length) + cfor(0)(_ < s.txs.length, _ + 1) { i => w.putLong(s.txs(i)) } + w.putUInt(s.boxes.length) + cfor(0)(_ < s.boxes.length, _ + 1) { i => w.putLong(s.boxes(i)) } + w.putInt(s.boxSegmentCount) + w.putInt(s.txSegmentCount) + } + + def parse(r: Reader, s: Segment[_]): Unit = { + val txnsLen: Long = r.getUInt() + cfor(0)(_ < txnsLen, _ + 1) { _ => s.txs.+=(r.getLong()) } + val boxesLen: Long = r.getUInt() + cfor(0)(_ < boxesLen, _ + 1) { _ => s.boxes.+=(r.getLong()) } + s.boxSegmentCount = r.getInt() + s.txSegmentCount = r.getInt() + } + +} diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala index 3385322366..2b4559af84 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala @@ -4,7 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine import org.ergoplatform.modifiers.{BlockSection, NetworkObjectTypeId} import org.ergoplatform.modifiers.history.HistoryModifierSerializer import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.nodeView.history.extra.{ExtraIndex, ExtraIndexSerializer, IndexedErgoAddress} +import org.ergoplatform.nodeView.history.extra.{ExtraIndex, ExtraIndexSerializer, Segment} import org.ergoplatform.settings.{Algos, CacheSettings, ErgoSettings} import scorex.core.utils.ScorexEncoding import scorex.db.{ByteArrayWrapper, LDBFactory, LDBKVStore} @@ -94,8 +94,8 @@ class HistoryStorage private(indexStore: LDBKVStore, objectsStore: LDBKVStore, e ExtraIndexSerializer.parseBytesTry(bytes) match { case Success(pm) => log.trace(s"Cache miss for existing index $id") - if(pm.isInstanceOf[IndexedErgoAddress]){ - extraCache.put(pm.id, pm) // only cache addresses + if(pm.isInstanceOf[Segment[_]]){ + extraCache.put(pm.id, pm) // cache all segment type objects } Some(pm) case Failure(_) => @@ -151,7 +151,7 @@ class HistoryStorage private(indexStore: LDBKVStore, objectsStore: LDBKVStore, e def insertExtra(indexesToInsert: Array[(Array[Byte], Array[Byte])], objectsToInsert: Array[ExtraIndex]): Unit = { extraStore.insert( - objectsToInsert.map(mod => (mod.serializedId)), + objectsToInsert.map(mod => mod.serializedId), objectsToInsert.map(mod => ExtraIndexSerializer.toBytes(mod)) ) cfor(0)(_ < objectsToInsert.length, _ + 1) { i => val ei = objectsToInsert(i); extraCache.put(ei.id, ei)} diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala index e53d2f2346..1d6371f430 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala @@ -63,7 +63,7 @@ trait HeadersProcessor extends ToDownloadProcessor with PopowProcessor with Scor def isSemanticallyValid(modifierId: ModifierId): ModifierSemanticValidity - // todo for performance reasons we may just use key like s"score$id" but this will require to redownload blockchain + // todo for performance reasons we may just use key like s"score$id" but this will require to resync the blockchain protected def headerScoreKey(id: ModifierId): ByteArrayWrapper = ByteArrayWrapper(Algos.hash("score".getBytes(ErgoHistory.CharsetName) ++ idToBytes(id))) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index 8e5431c369..75f626db88 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -6,7 +6,6 @@ import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransacti import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId import org.ergoplatform.nodeView.state.{ErgoState, UtxoState} import org.ergoplatform.settings.{ErgoSettings, MonetarySettings, NodeConfigurationSettings} -import scorex.core.transaction.state.TransactionValidation import scorex.util.{ModifierId, ScorexLogging, bytesToId} import OrderedTxPool.weighted import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption @@ -228,7 +227,8 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, // Allow proceeded transaction to spend outputs of pooled transactions. val utxoWithPool = utxo.withUnconfirmedTransactions(getAll) if (tx.inputIds.forall(inputBoxId => utxoWithPool.boxById(inputBoxId).isDefined)) { - utxoWithPool.validateWithCost(tx, Some(utxo.stateContext), costLimit, None) match { + val validationContext = utxo.stateContext.simplifiedUpcoming() + utxoWithPool.validateWithCost(tx, validationContext, costLimit, None) match { case Success(cost) => acceptIfNoDoubleSpend(unconfirmedTx.withCost(cost), validationStartTime) case Failure(ex) => @@ -238,15 +238,6 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, val exc = new Exception("not all utxos in place yet") this -> new ProcessingOutcome.Declined(exc, validationStartTime) } - case validator: TransactionValidation => - // transaction validation currently works only for UtxoState, so this branch currently - // will not be triggered probably - validator.validateWithCost(tx, costLimit) match { - case Success(cost) => - acceptIfNoDoubleSpend(unconfirmedTx.withCost(cost), validationStartTime) - case Failure(ex) => - this.invalidate(unconfirmedTx) -> new ProcessingOutcome.Invalidated(ex, validationStartTime) - } case _ => // Accept transaction in case of "digest" state. Transactions are not downloaded in this mode from other // peers though, so such transactions can come from the local wallet only. diff --git a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala index dc3644897f..4290d96ecc 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala @@ -24,7 +24,7 @@ import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging, bytesToId} import sigmastate.AtLeast import sigmastate.Values.{ByteArrayConstant, ErgoTree, IntConstant, SigmaPropConstant} -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.serialization.ValueSerializer import spire.syntax.all.cfor diff --git a/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala b/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala index 83f302a3b5..ce478458ad 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala @@ -8,7 +8,7 @@ import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.nodeView.history.ErgoHistory import org.ergoplatform.nodeView.history.storage.modifierprocessors.ExtensionValidator -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import org.ergoplatform.settings.ValidationRules._ import org.ergoplatform.settings._ import scorex.core.serialization.{BytesSerializable, ErgoSerializer} @@ -17,10 +17,10 @@ import scorex.core.validation.{InvalidModifier, ModifierValidator, ValidationSta import scorex.crypto.authds.ADDigest import scorex.util.ScorexLogging import scorex.util.serialization.{Reader, Writer} -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants.EcPointType import sigmastate.eval.Extensions.ArrayOps import sigmastate.eval.SigmaDsl -import special.collection.Coll +import sigma.Coll import scala.util.{Failure, Success, Try} @@ -38,9 +38,9 @@ case class UpcomingStateContext(override val lastHeaders: Seq[Header], extends ErgoStateContext(lastHeaders, lastExtensionOpt, genesisStateDigest, currentParameters, validationSettings, votingData)(ergoSettings) { - override def sigmaPreHeader: special.sigma.PreHeader = PreHeader.toSigma(predictedHeader) + override def sigmaPreHeader: sigma.PreHeader = PreHeader.toSigma(predictedHeader) - override def sigmaLastHeaders: Coll[special.sigma.Header] = { + override def sigmaLastHeaders: Coll[sigma.Header] = { SigmaDsl.Colls.fromArray(lastHeaders.map(h => Header.toSigma(h)).toArray) } @@ -66,7 +66,7 @@ class ErgoStateContext(val lastHeaders: Seq[Header], val validationSettings: ErgoValidationSettings, val votingData: VotingData) (implicit val ergoSettings: ErgoSettings) - extends ErgoLikeStateContext + extends BlockchainStateContext with BytesSerializable with ScorexEncoding with ScorexLogging { @@ -76,10 +76,10 @@ class ErgoStateContext(val lastHeaders: Seq[Header], private val votingSettings = ergoSettings.chainSettings.voting private val popowAlgos = new NipopowAlgos(ergoSettings.chainSettings) - override def sigmaPreHeader: special.sigma.PreHeader = + override def sigmaPreHeader: sigma.PreHeader = PreHeader.toSigma(lastHeaders.headOption.getOrElse(PreHeader.fake)) - override def sigmaLastHeaders: Coll[special.sigma.Header] = + override def sigmaLastHeaders: Coll[sigma.Header] = SigmaDsl.Colls.fromArray(lastHeaders.drop(1).map(h => Header.toSigma(h)).toArray) // todo remove from ErgoLikeContext and from ErgoStateContext @@ -109,6 +109,9 @@ class ErgoStateContext(val lastHeaders: Seq[Header], override def serializer: ErgoSerializer[M] = ErgoStateContextSerializer(ergoSettings) + /** + * @return state context corresponding to a block after last known one with fields provided + */ def upcoming(minerPk: EcPointType, timestamp: Long, nBits: Long, @@ -124,6 +127,25 @@ class ErgoStateContext(val lastHeaders: Seq[Header], calculatedValidationSettings, votingData) } + /** + * @return state context corresponding to a block after last known one + * with fields filled with (kind of) default values + */ + def simplifiedUpcoming(): UpcomingStateContext = { + val minerPk = org.ergoplatform.mining.group.generator + val version = lastHeaderOpt.map(_.version).getOrElse(Header.InitialVersion) + val nBits = lastHeaderOpt.map(_.nBits).getOrElse(ergoSettings.chainSettings.initialNBits) + val timestamp = lastHeaderOpt.map(_.timestamp + 1).getOrElse(System.currentTimeMillis()) + val votes = Array.emptyByteArray + val proposedUpdate = ErgoValidationSettingsUpdate.empty + val upcomingHeader = PreHeader(lastHeaderOpt, version, minerPk, timestamp, nBits, votes) + val height = ErgoHistory.heightOf(lastHeaderOpt) + 1 + val (calculatedParams, updated) = currentParameters.update(height, forkVote = false, votingData.epochVotes, proposedUpdate, votingSettings) + val calculatedValidationSettings = validationSettings.updated(updated) + UpcomingStateContext(lastHeaders, lastExtensionOpt, upcomingHeader, genesisStateDigest, calculatedParams, + calculatedValidationSettings, votingData) + } + protected def checkForkVote(height: Height): Unit = { if (currentParameters.softForkStartingHeight.nonEmpty) { val startingHeight = currentParameters.softForkStartingHeight.get diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala index 37003d4241..a4254f07b0 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -13,7 +13,6 @@ import org.ergoplatform.settings.{Algos, ErgoSettings, Parameters} import org.ergoplatform.utils.LoggingUtil import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedModifier import scorex.core._ -import scorex.core.transaction.state.TransactionValidation import scorex.core.utils.ScorexEncoding import scorex.core.validation.ModifierValidator import scorex.crypto.authds.avltree.batch._ @@ -38,7 +37,6 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 override val store: LDBVersionedStore, override protected val ergoSettings: ErgoSettings) extends ErgoState[UtxoState] - with TransactionValidation with UtxoStateReader with ScorexEncoding { diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala index b856643302..f717464185 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala @@ -9,8 +9,7 @@ import org.ergoplatform.settings.{Algos, ErgoSettings} import org.ergoplatform.settings.Algos.HF import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.wallet.interpreter.ErgoInterpreter -import scorex.core.transaction.state.TransactionValidation -import scorex.core.transaction.state.TransactionValidation.TooHighCostError +import scorex.core.transaction.TooHighCostError import scorex.core.validation.MalformedModifierError import scorex.crypto.authds.avltree.batch.{Lookup, PersistentBatchAVLProver, VersionedLDBAVLStorage} import scorex.crypto.authds.{ADDigest, ADKey, SerializedAdProof} @@ -23,7 +22,7 @@ import scala.util.{Failure, Success, Try} * state representation (so functions to generate UTXO set modifiction proofs, do stateful transaction validation, * get UTXOs are there */ -trait UtxoStateReader extends ErgoStateReader with UtxoSetSnapshotPersistence with TransactionValidation { +trait UtxoStateReader extends ErgoStateReader with UtxoSetSnapshotPersistence { protected implicit val hf: HF = Algos.hash @@ -46,10 +45,9 @@ trait UtxoStateReader extends ErgoStateReader with UtxoSetSnapshotPersistence wi * or state context from the previous block if not */ def validateWithCost(tx: ErgoTransaction, - stateContextOpt: Option[ErgoStateContext], + context: ErgoStateContext, costLimit: Int, interpreterOpt: Option[ErgoInterpreter]): Try[Int] = { - val context = stateContextOpt.getOrElse(stateContext) val parameters = context.currentParameters.withBlockCost(costLimit) val verifier = interpreterOpt.getOrElse(ErgoInterpreter(parameters)) @@ -61,27 +59,16 @@ trait UtxoStateReader extends ErgoStateReader with UtxoSetSnapshotPersistence wi context, accumulatedCost = 0L)(verifier) match { case Success(txCost) if txCost > costLimit => - Failure(TooHighCostError(s"Transaction $tx has too high cost $txCost")) + Failure(TooHighCostError(tx, Some(txCost))) case Success(txCost) => Success(txCost) case Failure(mme: MalformedModifierError) if mme.message.contains("CostLimitException") => - Failure(TooHighCostError(s"Transaction $tx has too high cost")) + Failure(TooHighCostError(tx, None)) case f: Failure[_] => f } } } - /** - * Validate transaction as if it was included at the end of the last block. - * This validation does not guarantee that transaction will be valid in future - * as soon as state (both UTXO set and state context) will change. - * - * Used in mempool. - */ - override def validateWithCost(tx: ErgoTransaction, maxTxCost: Int): Try[Int] = { - validateWithCost(tx, None, maxTxCost, None) - } - /** * * @param fb - ergo full block diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 0d9732964d..3c1594e091 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -25,7 +25,7 @@ import scorex.core.VersionTag import scorex.core.utils.ScorexEncoding import scorex.util.{ModifierId, ScorexLogging} import sigmastate.Values.SigmaBoolean -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala index dab82fe87b..fd2d38ba76 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala @@ -20,7 +20,7 @@ import org.ergoplatform.{ErgoBox, P2PKAddress} import scorex.core.NodeViewComponent import scorex.util.ModifierId import sigmastate.Values.SigmaBoolean -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol.DLogProverInput import java.util.concurrent.TimeUnit import scala.concurrent.Future diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala index 34c82fcded..7ff6ab2d72 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala @@ -24,8 +24,8 @@ import org.ergoplatform.wallet.utils.FileUtils import scorex.util.encode.Base16 import scorex.util.{ModifierId} import sigmastate.Values.SigmaBoolean -import sigmastate.basics.DLogProtocol.DLogProverInput -import special.collection.Extensions.CollBytesOps +import sigmastate.crypto.DLogProtocol.DLogProverInput +import sigma.Extensions.CollBytesOps import java.io.FileNotFoundException import scala.collection.compat.immutable.ArraySeq diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala index 669f69da39..247c6cd116 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala @@ -20,8 +20,9 @@ import org.ergoplatform.wallet.interpreter.ErgoProvingInterpreter import org.ergoplatform.wallet.mnemonic.Mnemonic import org.ergoplatform.wallet.transactions.TransactionBuilder import scorex.util.ScorexLogging +import sigma.Colls import sigmastate.Values.ByteArrayConstant -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.utils.Extensions._ diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/models/ChangeBox.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/models/ChangeBox.scala index 9006e28ebd..769237ecd7 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/models/ChangeBox.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/models/ChangeBox.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView.wallet.models -import io.circe.generic.encoding.DerivedObjectEncoder.deriveEncoder +import io.circe.generic.encoding.DerivedAsObjectEncoder.deriveEncoder import io.circe.syntax._ import io.circe.{Encoder, Json, KeyEncoder} import scorex.util.ModifierId diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/models/CollectedBoxes.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/models/CollectedBoxes.scala index 58fb14001c..6fbb4f8f5a 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/models/CollectedBoxes.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/models/CollectedBoxes.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView.wallet.models -import io.circe.generic.encoding.DerivedObjectEncoder.deriveEncoder +import io.circe.generic.encoding.DerivedAsObjectEncoder.deriveEncoder import io.circe.syntax._ import io.circe.{Encoder, Json} import org.ergoplatform.ErgoBox @@ -21,4 +21,5 @@ object CollectedBoxes extends JsonCodecs { "boxes" -> request.boxes.asJson, "changeBoxes" -> request.changeBoxes.asJson ) + } diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicate.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicate.scala index 402475a8aa..133c1736ef 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicate.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicate.scala @@ -3,7 +3,7 @@ package org.ergoplatform.nodeView.wallet.scanning import org.ergoplatform.ErgoBox import sigmastate.Values.EvaluatedValue import sigmastate.{SType, Values} -import special.collection.Extensions._ +import sigma.Extensions._ /** * Basic interface for box scanning predicate functionality diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicateJsonCodecs.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicateJsonCodecs.scala index e8305fb331..c57b4923ff 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicateJsonCodecs.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/scanning/ScanningPredicateJsonCodecs.scala @@ -7,7 +7,7 @@ import org.ergoplatform.ErgoBox.RegisterId import org.ergoplatform.http.api.ApiCodecs import sigmastate.SType import sigmastate.Values.EvaluatedValue -import special.collection.Extensions._ +import sigma.Extensions._ object ScanningPredicateJsonCodecs extends ApiCodecs { diff --git a/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala b/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala index 7dc87a66b0..2660693fac 100644 --- a/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala +++ b/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala @@ -9,7 +9,7 @@ import sigmastate.SBoolean import sigmastate.Values.Value import sigmastate.eval.CompiletimeIRContext import sigmastate.lang.{CompilerSettings, SigmaCompiler, TransformingSigmaBuilder} -import special.collection.Coll +import sigma.Coll import scala.util.Try diff --git a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala index 5ceff6e42a..41bdd67a43 100644 --- a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala @@ -13,7 +13,7 @@ import org.ergoplatform.{ErgoAddressEncoder, ErgoApp, P2PKAddress} import scorex.core.settings.{ScorexSettings, SettingsReaders} import scorex.util.ScorexLogging import scorex.util.encode.Base16 -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import java.net.{InetAddress, URL} import scala.util.Try diff --git a/src/main/scala/org/ergoplatform/settings/Parameters.scala b/src/main/scala/org/ergoplatform/settings/Parameters.scala index f2f896335c..5a12fa813d 100644 --- a/src/main/scala/org/ergoplatform/settings/Parameters.scala +++ b/src/main/scala/org/ergoplatform/settings/Parameters.scala @@ -12,7 +12,7 @@ import scala.util.Try import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionCandidate} import Extension.SystemParametersPrefix -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk.BlockchainParameters /** * System parameters which could be readjusted via collective miners decision. @@ -20,7 +20,7 @@ import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters class Parameters(val height: Height, val parametersTable: Map[Byte, Int], val proposedUpdate: ErgoValidationSettingsUpdate) - extends ErgoLikeParameters { + extends BlockchainParameters { import Parameters._ diff --git a/src/main/scala/org/ergoplatform/settings/ReemissionSettings.scala b/src/main/scala/org/ergoplatform/settings/ReemissionSettings.scala index 1acc476597..aa869754a9 100644 --- a/src/main/scala/org/ergoplatform/settings/ReemissionSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/ReemissionSettings.scala @@ -6,7 +6,7 @@ import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import scorex.util.ModifierId import scorex.util.encode.Base16 import sigmastate.utils.Extensions.ModifierIdOps -import special.collection.Coll +import sigma.Coll /** * Configuration section for re-emission (EIP27) parameters * diff --git a/src/main/scala/org/ergoplatform/utils/BoxUtils.scala b/src/main/scala/org/ergoplatform/utils/BoxUtils.scala index f6655aca85..37931c9c7f 100644 --- a/src/main/scala/org/ergoplatform/utils/BoxUtils.scala +++ b/src/main/scala/org/ergoplatform/utils/BoxUtils.scala @@ -1,13 +1,12 @@ package org.ergoplatform.utils -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate} +import org.ergoplatform.ErgoBox.{AdditionalRegisters, TokenId} import org.ergoplatform.settings.{Algos, Parameters} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate} import scorex.util.ModifierId -import sigmastate.SType -import sigmastate.Values.{ErgoTree, EvaluatedValue} +import sigma.{Coll, Colls} +import sigmastate.Values.ErgoTree import sigmastate.eval._ -import special.collection.Coll object BoxUtils { @@ -19,7 +18,7 @@ object BoxUtils { @inline def minimalErgoAmountSimulated(script: ErgoTree, tokens: Coll[(TokenId, Long)] = Colls.emptyColl, - additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map(), + additionalRegisters: AdditionalRegisters = Map(), parameters: Parameters): Long = { val candidateMock = new ErgoBoxCandidate(value = Long.MaxValue, script, creationHeight = Int.MaxValue, tokens, additionalRegisters) val mockId = ModifierId @@ Algos.encode(scorex.util.Random.randomBytes(32)) diff --git a/src/main/scala/scorex/core/api/http/ApiDirectives.scala b/src/main/scala/scorex/core/api/http/ApiDirectives.scala index 7f17b5ceab..bebba07910 100644 --- a/src/main/scala/scorex/core/api/http/ApiDirectives.scala +++ b/src/main/scala/scorex/core/api/http/ApiDirectives.scala @@ -14,8 +14,11 @@ trait ApiDirectives extends CorsHandler with ScorexEncoding { case None => reject(AuthorizationFailedRejection) case Some(key) => val keyHashStr: String = encoder.encode(Blake2b256(key)) - if (settings.apiKeyHash.contains(keyHashStr)) pass - else reject(AuthorizationFailedRejection) + if (settings.apiKeyHash.contains(keyHashStr)) { + pass + } else { + reject(AuthorizationFailedRejection) + } } } diff --git a/src/main/scala/scorex/core/api/http/ApiError.scala b/src/main/scala/scorex/core/api/http/ApiError.scala index c11153a216..39904c0196 100644 --- a/src/main/scala/scorex/core/api/http/ApiError.scala +++ b/src/main/scala/scorex/core/api/http/ApiError.scala @@ -27,9 +27,7 @@ case class ApiError(statusCode: StatusCode, reason: String = "") { object ApiError { - def apply(s: String): Route = InternalError(s) def apply(e: Throwable): Route = InternalError(safeMessage(e)) - def apply(causes: Seq[Throwable]): Route = InternalError(mkString(causes)) def mkString(causes: Seq[Throwable]): String = causes.map(safeMessage).mkString(", ") private def safeMessage(e: Throwable): String = Option(e.getMessage).getOrElse(e.toString) diff --git a/src/main/scala/scorex/core/network/ConnectedPeer.scala b/src/main/scala/scorex/core/network/ConnectedPeer.scala index 55ac009a17..b8e28ccd86 100644 --- a/src/main/scala/scorex/core/network/ConnectedPeer.scala +++ b/src/main/scala/scorex/core/network/ConnectedPeer.scala @@ -10,12 +10,10 @@ import scorex.core.network.peer.PeerInfo * * @param connectionId - connection address * @param handlerRef - reference to PeerConnectionHandler that is responsible for communication with this peer - * @param lastMessage - timestamp of last received message * @param peerInfo - information about this peer. May be None if peer is connected, but is not handshaked yet */ case class ConnectedPeer(connectionId: ConnectionId, handlerRef: ActorRef, - var lastMessage: Long, peerInfo: Option[PeerInfo]) { override def hashCode(): Int = connectionId.remoteAddress.hashCode() @@ -45,7 +43,7 @@ object ConnectedPeer { val optionalFields = List( peer.peerInfo.map(_.peerSpec.protocolVersion.toString).map("version" -> _.asJson), - Option(peer.lastMessage).filter(_ != 0L).map("lastMessage" -> _.asJson) + peer.peerInfo.map(_.lastStoredActivityTime).filter(_ != 0L).map("lastMessage" -> _.asJson), ).flatten val fields = addressField :: optionalFields Json.obj(fields:_*) diff --git a/src/main/scala/scorex/core/network/NetworkController.scala b/src/main/scala/scorex/core/network/NetworkController.scala index 2ac5376e1c..16995e7e69 100644 --- a/src/main/scala/scorex/core/network/NetworkController.scala +++ b/src/main/scala/scorex/core/network/NetworkController.scala @@ -116,25 +116,24 @@ class NetworkController(ergoSettings: ErgoSettings, private def time(): Time = System.currentTimeMillis() private def businessLogic: Receive = { - //a message coming in from another peer + // a message coming in from another peer case msg@Message(spec, _, Some(remote)) => messageHandlers.get(spec.messageCode) match { case Some(handler) => handler ! msg // forward the message to the appropriate handler for processing case None => log.error(s"No handlers found for message $remote: " + spec.messageCode) } - // Update last seen message timestamps, global and peer's, with the message timestamp + // Update last seen message timestamps with the message timestamp val remoteAddress = remote.connectionId.remoteAddress connections.get(remoteAddress) match { case Some(cp) => val now = time() lastIncomingMessageTime = now - cp.lastMessage = now - // Update peer's last activity time every X minutes inside PeerInfo - cp.peerInfo.foreach { x => - if ((now - x.lastStoredActivityTime) > activityDelta) { - val peerInfo = x.copy(lastStoredActivityTime = now) - peerManagerRef ! AddOrUpdatePeer(peerInfo) + // Update peer's last activity time every ${activityDelta} minutes inside PeerInfo + cp.peerInfo.foreach { peerInfo => + if ((now - peerInfo.lastStoredActivityTime) > activityDelta) { + val peerInfoUpdated = peerInfo.copy(lastStoredActivityTime = now) + peerManagerRef ! AddOrUpdatePeer(peerInfoUpdated) } } @@ -297,13 +296,15 @@ class NetworkController(ergoSettings: ErgoSettings, () => { // Drop connections with peers if they seem to be inactive val now = time() - connections.values.foreach { cp => - val lastSeen = cp.lastMessage - val timeout = networkSettings.inactiveConnectionDeadline.toMillis - val delta = now - lastSeen - if (delta > timeout) { - log.info(s"Dropping connection with ${cp.peerInfo}, last seen ${delta / 1000.0} seconds ago") - cp.handlerRef ! CloseConnection + connections.values.foreach { cp => + cp.peerInfo.foreach { peerInfo => + val lastSeen = peerInfo.lastStoredActivityTime + val timeout = networkSettings.inactiveConnectionDeadline.toMillis + val delta = now - lastSeen + if (delta > timeout && lastSeen > 0) { + log.info(s"Dropping connection with ${peerInfo}, last seen ${delta / 1000.0} seconds ago") + cp.handlerRef ! CloseConnection + } } } } @@ -400,7 +401,7 @@ class NetworkController(ergoSettings: ErgoSettings, val handler = context.actorOf(handlerProps) // launch connection handler context.watch(handler) - val connectedPeer = ConnectedPeer(connectionId, handler, time(), None) + val connectedPeer = ConnectedPeer(connectionId, handler, None) connections += connectionId.remoteAddress -> connectedPeer unconfirmedConnections -= connectionId.remoteAddress } @@ -424,9 +425,10 @@ class NetworkController(ergoSettings: ErgoSettings, peerManagerRef ! RemovePeer(peerAddress) connections -= connectedPeer.connectionId.remoteAddress } else { - peerManagerRef ! AddOrUpdatePeer(peerInfo) + val newPeerInfo = peerInfo.copy(lastStoredActivityTime = time()) + peerManagerRef ! AddOrUpdatePeer(newPeerInfo) - val updatedConnectedPeer = connectedPeer.copy(peerInfo = Some(peerInfo)) + val updatedConnectedPeer = connectedPeer.copy(peerInfo = Some(newPeerInfo)) connections += remoteAddress -> updatedConnectedPeer context.system.eventStream.publish(HandshakedPeer(updatedConnectedPeer)) } diff --git a/src/main/scala/scorex/core/network/PeerConnectionHandler.scala b/src/main/scala/scorex/core/network/PeerConnectionHandler.scala index 80cbbce51b..84bb3ac2f4 100644 --- a/src/main/scala/scorex/core/network/PeerConnectionHandler.scala +++ b/src/main/scala/scorex/core/network/PeerConnectionHandler.scala @@ -72,7 +72,7 @@ class PeerConnectionHandler(scorexSettings: ScorexSettings, log.info(s"Got a Handshake from $connectionId") val peerInfo = PeerInfo(receivedHandshake.peerSpec, System.currentTimeMillis(), Some(direction)) - val peer = ConnectedPeer(connectionDescription.connectionId, self, 0, Some(peerInfo)) + val peer = ConnectedPeer(connectionDescription.connectionId, self, Some(peerInfo)) selfPeer = Some(peer) networkControllerRef ! Handshaked(peerInfo) diff --git a/src/main/scala/scorex/core/network/PeerSpec.scala b/src/main/scala/scorex/core/network/PeerSpec.scala index 47b9181442..99faeb6c2d 100644 --- a/src/main/scala/scorex/core/network/PeerSpec.scala +++ b/src/main/scala/scorex/core/network/PeerSpec.scala @@ -35,8 +35,6 @@ case class PeerSpec(agentName: String, lazy val publicUrlOpt: Option[URL] = features.collectFirst { case RestApiUrlPeerFeature(url) => url } - def reachablePeer: Boolean = address.isDefined - def address: Option[InetSocketAddress] = declaredAddress orElse localAddressOpt } diff --git a/src/main/scala/scorex/core/network/peer/PeerInfo.scala b/src/main/scala/scorex/core/network/peer/PeerInfo.scala index d37bbc2242..b95bafa938 100644 --- a/src/main/scala/scorex/core/network/peer/PeerInfo.scala +++ b/src/main/scala/scorex/core/network/peer/PeerInfo.scala @@ -37,7 +37,7 @@ object PeerInfo { */ def fromAddress(address: InetSocketAddress): PeerInfo = { val peerSpec = PeerSpec("unknown", Version.initial, s"unknown-$address", Some(address), Seq()) - PeerInfo(peerSpec, 0L, None) + PeerInfo(peerSpec, 0L, None, 0L) } } @@ -57,6 +57,6 @@ object PeerInfoSerializer extends ErgoSerializer[PeerInfo] { val lastHandshake = r.getLong() val connectionType = r.getOption(if (r.getUByte() != 0) Incoming else Outgoing) val peerSpec = PeerSpecSerializer.parse(r) - PeerInfo(peerSpec, lastHandshake, connectionType) + PeerInfo(peerSpec, lastHandshake, connectionType, lastStoredActivityTime = 0L) } } diff --git a/src/main/scala/scorex/core/transaction/TooHighCostError.scala b/src/main/scala/scorex/core/transaction/TooHighCostError.scala new file mode 100644 index 0000000000..e86d3d6189 --- /dev/null +++ b/src/main/scala/scorex/core/transaction/TooHighCostError.scala @@ -0,0 +1,9 @@ +package scorex.core.transaction + +import org.ergoplatform.modifiers.mempool.ErgoTransaction + +/** + * Exception which is indicating that transaction had too high cost during validation + */ +case class TooHighCostError(tx: ErgoTransaction, txCost: Option[Int]) + extends Exception(s"Transaction $tx has too high cost ${txCost.map(_.toString).getOrElse("")}") diff --git a/src/main/scala/scorex/core/transaction/state/StateFeature.scala b/src/main/scala/scorex/core/transaction/state/StateFeature.scala deleted file mode 100644 index ebccd8a23f..0000000000 --- a/src/main/scala/scorex/core/transaction/state/StateFeature.scala +++ /dev/null @@ -1,21 +0,0 @@ -package scorex.core.transaction.state - -import org.ergoplatform.modifiers.mempool.ErgoTransaction -import scala.util.Try - -/** - * Basic trait for features supported by state representation - */ -trait StateFeature - -/** - * Instance of this trait supports stateful validation of any transaction - */ -trait TransactionValidation extends StateFeature { - def validateWithCost(tx: ErgoTransaction, maxTxCost: Int): Try[Int] -} - -object TransactionValidation { - case class TooHighCostError(message: String) extends Exception(message) -} - diff --git a/src/test/scala/org/ergoplatform/http/routes/BlocksApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/BlocksApiRouteSpec.scala index ed0e328ed4..7edef059f5 100644 --- a/src/test/scala/org/ergoplatform/http/routes/BlocksApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/BlocksApiRouteSpec.scala @@ -3,6 +3,8 @@ package org.ergoplatform.http.routes import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes, UniversalEntity} import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport +import io.circe.Json import io.circe.syntax._ import org.ergoplatform.http.api.BlocksApiRoute import org.ergoplatform.modifiers.ErgoFullBlock @@ -13,9 +15,11 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import scorex.util.ModifierId -class BlocksApiRouteSpec extends AnyFlatSpec +class BlocksApiRouteSpec + extends AnyFlatSpec with Matchers with ScalatestRouteTest + with FailFastCirceSupport with Stubs { val prefix = "/blocks" @@ -23,19 +27,23 @@ class BlocksApiRouteSpec extends AnyFlatSpec val route: Route = BlocksApiRoute(nodeViewRef, digestReadersRef, settings).route val headerIdBytes: ModifierId = history.lastHeaders(1).headers.head.id - val headerIdString: String = Algos.encode(headerIdBytes) + val headerIdString: String = Algos.encode(headerIdBytes) it should "get last blocks" in { Get(prefix) ~> route ~> check { status shouldBe StatusCodes.OK - history.headerIdsAt(0, 50).map(Algos.encode).asJson.toString() shouldEqual responseAs[String] + history + .headerIdsAt(0, 50) + .map(Algos.encode) + .asJson shouldEqual responseAs[Json] } } it should "post block correctly" in { - val (st, bh) = createUtxoState(settings) + val (st, bh) = createUtxoState(settings) val block: ErgoFullBlock = validFullBlock(parentOpt = None, st, bh) - val blockJson: UniversalEntity = HttpEntity(block.asJson.toString).withContentType(ContentTypes.`application/json`) + val blockJson: UniversalEntity = + HttpEntity(block.asJson.toString).withContentType(ContentTypes.`application/json`) Post(prefix, blockJson) ~> route ~> check { status shouldBe StatusCodes.OK } @@ -44,37 +52,62 @@ class BlocksApiRouteSpec extends AnyFlatSpec it should "get last headers" in { Get(prefix + "/lastHeaders/1") ~> route ~> check { status shouldBe StatusCodes.OK - history.lastHeaders(1).headers.map(_.asJson).asJson.toString() shouldEqual responseAs[String] + history + .lastHeaders(1) + .headers + .map(_.asJson) + .asJson shouldEqual responseAs[Json] } } it should "get block at height" in { Get(prefix + "/at/0") ~> route ~> check { status shouldBe StatusCodes.OK - history.headerIdsAtHeight(0).map(Algos.encode).asJson.toString() shouldEqual responseAs[String] + history + .headerIdsAtHeight(0) + .map(Algos.encode) + .asJson shouldEqual responseAs[Json] } } it should "get chain slice" in { Get(prefix + "/chainSlice?fromHeight=0") ~> route ~> check { status shouldBe StatusCodes.OK - chain.map(_.header).asJson.toString() shouldEqual responseAs[String] + chain.map(_.header).asJson shouldEqual responseAs[Json] } Get(prefix + "/chainSlice?fromHeight=2&toHeight=4") ~> route ~> check { status shouldBe StatusCodes.OK - chain.slice(2, 4).map(_.header).asJson.toString() shouldEqual responseAs[String] + chain.slice(2, 4).map(_.header).asJson shouldEqual responseAs[Json] } } it should "get block by header id" in { Get(prefix + "/" + headerIdString) ~> route ~> check { status shouldBe StatusCodes.OK - val expected = history.typedModifierById[Header](headerIdBytes) + val expected = history + .typedModifierById[Header](headerIdBytes) .flatMap(history.getFullBlock) .map(_.asJson) .get - .toString - responseAs[String] shouldEqual expected + + responseAs[Json] shouldEqual expected + } + } + + it should "get blocks by header ids" in { + val headerIdsBytes = history.lastHeaders(10).headers + val headerIdsString: Seq[String] = headerIdsBytes.map(h => Algos.encode(h.id)) + + Post(prefix + "/headerIds", headerIdsString.asJson) ~> route ~> check { + status shouldBe StatusCodes.OK + + val expected = headerIdsBytes + .map(_.id) + .flatMap(headerId => + history.typedModifierById[Header](headerId).flatMap(history.getFullBlock) + ) + + responseAs[Seq[ErgoFullBlock]] shouldEqual expected } } @@ -86,18 +119,18 @@ class BlocksApiRouteSpec extends AnyFlatSpec .flatMap(history.getFullBlock) .map(_.header.asJson) .get - .toString - responseAs[String] shouldEqual expected + + responseAs[Json] shouldEqual expected } } it should "get transactions by header id" in { Get(prefix + "/" + headerIdString + "/transactions") ~> route ~> check { status shouldBe StatusCodes.OK - val header = history.typedModifierById[Header](headerIdBytes).value + val header = history.typedModifierById[Header](headerIdBytes).value val fullBlock = history.getFullBlock(header).value - val expected = fullBlock.blockTransactions.asJson.toString - responseAs[String] shouldEqual expected + val expected = fullBlock.blockTransactions.asJson + responseAs[Json] shouldEqual expected } } diff --git a/src/test/scala/org/ergoplatform/http/routes/ScanApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/ScanApiRouteSpec.scala index afee4a72b3..fb4956190f 100644 --- a/src/test/scala/org/ergoplatform/http/routes/ScanApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/ScanApiRouteSpec.scala @@ -252,7 +252,6 @@ class ScanApiRouteSpec extends AnyFlatSpec } } - it should "stop tracking a box" in { val scanIdBoxId = ScanIdBoxId(ScanId @@ (51: Short), ADKey @@ Random.randomBytes(32)) @@ -261,4 +260,16 @@ class ScanApiRouteSpec extends AnyFlatSpec } } + it should "generate scan for p2s rule" in { + Post(prefix + "/p2sRule", "Ms7smJmdbakqfwNo") ~> route ~> check { + status shouldBe StatusCodes.OK + val res = responseAs[Json] + res.hcursor.downField("scanId").as[Int].toOption.isDefined shouldBe true + } + + Post(prefix + "/p2sRule", "s7smJmdbakqfwNo") ~> route ~> check { + status shouldBe StatusCodes.BadRequest + } + } + } diff --git a/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala index f261e1769a..acd2fd8de5 100644 --- a/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala @@ -5,7 +5,7 @@ import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport import io.circe.Json -import org.ergoplatform.Pay2SAddress +import org.ergoplatform.{Pay2SAddress, Pay2SHAddress} import org.ergoplatform.settings.{Args, ErgoSettings} import org.ergoplatform.utils.Stubs import io.circe.syntax._ @@ -14,8 +14,8 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import scorex.util.encode.Base16 import sigmastate.SByte -import sigmastate.Values.{TrueLeaf, CollectionConstant, ErgoTree} -import sigmastate.serialization.{ValueSerializer, ErgoTreeSerializer} +import sigmastate.Values.{CollectionConstant, ErgoTree, TrueLeaf} +import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} class ScriptApiRouteSpec extends AnyFlatSpec @@ -74,19 +74,17 @@ class ScriptApiRouteSpec extends AnyFlatSpec check(assertion(responseAs[Json])) } - //todo: temporarily switched off due to https://github.com/ergoplatform/ergo/issues/936 -// it should "generate valid P2SHAddress form source" in { -// val suffix = "/p2shAddress" -// val assertion = (json: Json) => { -// status shouldBe StatusCodes.OK -// val addressStr = json.hcursor.downField("address").as[String].right.get -// ergoAddressEncoder.fromString(addressStr).get.addressTypePrefix shouldEqual Pay2SHAddress.addressTypePrefix -// } -// Post(prefix + suffix, Json.obj("source" -> scriptSource.asJson)) ~> route ~> check(assertion(responseAs[Json])) -// Post(prefix + suffix, Json.obj("source" -> scriptSourceSigProp.asJson)) ~> route ~> -// check(assertion(responseAs[Json])) -// } - + it should "generate valid P2SHAddress form source" in { + val suffix = "/p2shAddress" + val assertion = (json: Json) => { + status shouldBe StatusCodes.OK + val addressStr = json.hcursor.downField("address").as[String].right.get + addressEncoder.fromString(addressStr).get.addressTypePrefix shouldEqual Pay2SHAddress.addressTypePrefix + } + Post(prefix + suffix, Json.obj("source" -> scriptSource.asJson)) ~> route ~> check(assertion(responseAs[Json])) + Post(prefix + suffix, Json.obj("source" -> scriptSourceSigProp.asJson)) ~> route ~> + check(assertion(responseAs[Json])) + } it should "get through address <-> ergoTree round-trip" in { val suffix = "addressToTree" @@ -105,12 +103,13 @@ class ScriptApiRouteSpec extends AnyFlatSpec val p2pk = "3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN" Get(s"$prefix/$suffix/$p2pk") ~> route ~> check(assertion(responseAs[Json], p2pk)) - //todo: temporarily switched off due to https://github.com/ergoplatform/ergo/issues/936 -// val p2sh = "8UmyuJuQ3FS9ts7j72fn3fKChXSGzbL9WC" -// Get(s"$prefix/$suffix/$p2sh") ~> route ~> check(assertion(responseAs[Json], p2sh)) - val script = TrueLeaf val tree = ErgoTree.fromProposition(script) + + val p2sh = Pay2SHAddress.apply(tree).toString() + p2sh shouldBe "rbcrmKEYduUvADj9Ts3dSVSG27h54pgrq5fPuwB" + Get(s"$prefix/$suffix/$p2sh") ~> route ~> check(assertion(responseAs[Json], p2sh)) + val p2s = addressEncoder.toString(addressEncoder.fromProposition(tree).get) p2s shouldBe "Ms7smJwLGbUAjuWQ" Get(s"$prefix/$suffix/$p2s") ~> route ~> check(assertion(responseAs[Json], p2s)) @@ -138,9 +137,8 @@ class ScriptApiRouteSpec extends AnyFlatSpec val p2pk = "3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN" Get(s"$prefix/$suffix/$p2pk") ~> route ~> check(assertion(responseAs[Json], p2pk)) - //todo: temporarily switched off due to https://github.com/ergoplatform/ergo/issues/936 - // val p2sh = "8UmyuJuQ3FS9ts7j72fn3fKChXSGzbL9WC" - // Get(s"$prefix/$suffix/$p2sh") ~> route ~> check(assertion(responseAs[Json], p2sh)) + val p2sh = "rbcrmKEYduUvADj9Ts3dSVSG27h54pgrq5fPuwB" + Get(s"$prefix/$suffix/$p2sh") ~> route ~> check(assertion(responseAs[Json], p2sh)) val script = TrueLeaf val tree = ErgoTree.fromProposition(script) diff --git a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala index c13bd6cb0c..1e7860907f 100644 --- a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala @@ -7,7 +7,7 @@ import akka.http.scaladsl.testkit.ScalatestRouteTest import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport import io.circe.Json import io.circe.syntax._ -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} +import org.ergoplatform.ErgoBox.{AdditionalRegisters, NonMandatoryRegisterId, TokenId} import org.ergoplatform.http.api.{ApiCodecs, TransactionsApiRoute} import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} import org.ergoplatform.nodeView.ErgoReadersHolder.{GetDataFromHistory, GetReaders, Readers} @@ -22,7 +22,7 @@ import sigmastate.SType import sigmastate.Values.{ByteArrayConstant, EvaluatedValue} import sigmastate.eval.Extensions._ import sigmastate.eval._ -import special.collection.Extensions._ +import sigma.Extensions._ import java.net.InetSocketAddress import scala.concurrent.duration._ @@ -243,7 +243,7 @@ class TransactionApiRouteSpec extends AnyFlatSpec Map( ErgoBox.R4 -> ByteArrayConstant("name".getBytes("UTF-8")), ErgoBox.R5 -> ByteArrayConstant("4".getBytes("UTF-8")), - ).asJson + ).asInstanceOf[AdditionalRegisters].asJson Post(prefix + s"/unconfirmed/outputs/byRegisters", searchedRegs) ~> chainedRoute ~> check { status shouldBe StatusCodes.OK @@ -271,7 +271,7 @@ class TransactionApiRouteSpec extends AnyFlatSpec ErgoBox.R4 -> ByteArrayConstant("name".getBytes("UTF-8")), ErgoBox.R5 -> ByteArrayConstant("4".getBytes("UTF-8")), ErgoBox.R6 -> ByteArrayConstant("description".getBytes("UTF-8")), - ).asJson + ).asInstanceOf[AdditionalRegisters].asJson Post(prefix + s"/unconfirmed/outputs/byRegisters", searchedRegs) ~> chainedRoute ~> check { status shouldBe StatusCodes.OK diff --git a/src/test/scala/org/ergoplatform/http/routes/UtxoApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/UtxoApiRouteSpec.scala index 39d2ccf129..b5a2fa391b 100644 --- a/src/test/scala/org/ergoplatform/http/routes/UtxoApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/UtxoApiRouteSpec.scala @@ -14,7 +14,8 @@ import org.scalatest.matchers.should.Matchers import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 -class UtxoApiRouteSpec extends AnyFlatSpec +class UtxoApiRouteSpec + extends AnyFlatSpec with Matchers with ScalatestRouteTest with Stubs @@ -23,10 +24,11 @@ class UtxoApiRouteSpec extends AnyFlatSpec val prefix = "/utxo" - val route: Route = UtxoApiRoute(utxoReadersRef, utxoSettings.scorexSettings.restApi).route + val route: Route = + UtxoApiRoute(utxoReadersRef, utxoSettings.scorexSettings.restApi).route it should "get utxo box with /byId" in { - val box = utxoState.takeBoxes(1).head + val box = utxoState.takeBoxes(1).head val boxId = Base16.encode(box.id) Get(prefix + s"/byId/$boxId") ~> route ~> check { status shouldBe StatusCodes.OK @@ -36,7 +38,7 @@ class UtxoApiRouteSpec extends AnyFlatSpec } it should "get mempool box with withPool/byId" in { - val box = memPool.getAll.map(utx => utx.transaction).flatMap(_.outputs).head + val box = memPool.getAll.map(utx => utx.transaction).flatMap(_.outputs).head val boxId = Base16.encode(box.id) Get(prefix + s"/byId/$boxId") ~> route ~> check { status shouldBe StatusCodes.NotFound @@ -48,6 +50,19 @@ class UtxoApiRouteSpec extends AnyFlatSpec } } + it should "get all mempool boxes with withPool/byIds" in { + val boxes = memPool.getAll.map(utx => utx.transaction).flatMap(_.outputs) + val boxesEncoded = boxes.map(box => Base16.encode(box.id)) + + Post(prefix + "/withPool/byIds", boxesEncoded.asJson) ~> route ~> check { + status shouldBe StatusCodes.OK + responseAs[Seq[Json]] + .map(_.hcursor.downField("value").as[Long]) shouldEqual boxes.map(x => Right(x.value)) + responseAs[Seq[Json]] + .map(_.hcursor.downField("boxId").as[String]) shouldEqual boxesEncoded.map(x => Right(x)) + } + } + it should "not found utxo box with /byId" in { val boxId = Base16.encode(Blake2b256(utxoState.takeBoxes(1).head.id)) Get(prefix + s"/byId/$boxId") ~> route ~> check { @@ -56,12 +71,14 @@ class UtxoApiRouteSpec extends AnyFlatSpec } it should "get utxo box with /byIdBinary" in { - val box = utxoState.takeBoxes(1).head + val box = utxoState.takeBoxes(1).head val boxId = Base16.encode(box.id) Get(prefix + s"/byIdBinary/$boxId") ~> route ~> check { status shouldBe StatusCodes.OK responseAs[Json].hcursor.downField("boxId").as[String] shouldEqual Right(boxId) - val bytes = Base16.decode(responseAs[Json].hcursor.downField("bytes").as[String].toOption.get).get + val bytes = Base16 + .decode(responseAs[Json].hcursor.downField("bytes").as[String].toOption.get) + .get val boxRestored = ErgoBoxSerializer.parseBytes(bytes) box shouldEqual boxRestored } @@ -75,7 +92,7 @@ class UtxoApiRouteSpec extends AnyFlatSpec } it should "get pool box with /withPool/byIdBinary" in { - val box = memPool.getAll.map(utx => utx.transaction).flatMap(_.outputs).head + val box = memPool.getAll.map(utx => utx.transaction).flatMap(_.outputs).head val boxId = Base16.encode(box.id) Get(prefix + s"/byIdBinary/$boxId") ~> route ~> check { status shouldBe StatusCodes.NotFound @@ -83,7 +100,9 @@ class UtxoApiRouteSpec extends AnyFlatSpec Get(prefix + s"/withPool/byIdBinary/$boxId") ~> route ~> check { status shouldBe StatusCodes.OK responseAs[Json].hcursor.downField("boxId").as[String] shouldEqual Right(boxId) - val bytes = Base16.decode(responseAs[Json].hcursor.downField("bytes").as[String].toOption.get).get + val bytes = Base16 + .decode(responseAs[Json].hcursor.downField("bytes").as[String].toOption.get) + .get val boxRestored = ErgoBoxSerializer.parseBytes(bytes) box shouldEqual boxRestored } diff --git a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala index 5096dec09d..f018569c5e 100644 --- a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala +++ b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala @@ -54,7 +54,7 @@ class MempoolAuditorSpec extends AnyFlatSpec with NodeViewTestOps with ErgoTestH val boxes = ErgoState.newBoxes(genesis.transactions).find(_.ergoTree == Constants.TrueLeaf) boxes.nonEmpty shouldBe true - val script = s"{sigmaProp(HEIGHT == ${genesis.height})}" + val script = s"{sigmaProp(HEIGHT == ${genesis.height} + 1)}" val compiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) val prop = compiler.compile(emptyEnv, script).buildTree val tree = ErgoTree.fromProposition(prop.asSigmaProp) diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala index 66aa6a8c05..1c666e18e4 100644 --- a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala @@ -7,7 +7,7 @@ import org.ergoplatform.settings.MonetarySettings import org.ergoplatform.utils.{ErgoPropertyTest, RandomWrapper} import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.scalacheck.Gen -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import scala.concurrent.duration._ @@ -167,7 +167,7 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { val newBoxes = fromBigMempool.flatMap(_.outputs) val costs: Seq[Int] = fromBigMempool.map { tx => - us.validateWithCost(tx, Some(upcomingContext), Int.MaxValue, Some(verifier)).getOrElse { + us.validateWithCost(tx, upcomingContext, Int.MaxValue, Some(verifier)).getOrElse { val boxesToSpend = tx.inputs.map(i => newBoxes.find(b => b.id sameElements i.boxId).get) tx.statefulValidity(boxesToSpend, IndexedSeq(), upcomingContext).get diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala index 0fddaf5750..148d5ebffb 100644 --- a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala @@ -19,8 +19,8 @@ import org.ergoplatform.utils.ErgoTestHelpers import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} import org.scalatest.concurrent.Eventually import org.scalatest.flatspec.AnyFlatSpec -import sigmastate.basics.DLogProtocol -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol +import sigmastate.crypto.DLogProtocol.DLogProverInput import scala.concurrent.duration._ diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index 16e83f5d00..25258277e3 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -26,8 +26,8 @@ import org.scalatest.concurrent.Eventually import org.scalatest.flatspec.AnyFlatSpec import sigmastate.SigmaAnd import sigmastate.Values.{ErgoTree, SigmaPropConstant} -import sigmastate.basics.DLogProtocol -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol +import sigmastate.crypto.DLogProtocol.DLogProverInput import scala.annotation.tailrec import scala.concurrent.ExecutionContext.Implicits.global @@ -114,7 +114,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen val txCost = state.validateWithCost( ErgoTransaction(costlyTx.inputs, costlyTx.dataInputs, costlyTx.outputCandidates), - Some(r.s.stateContext), + r.s.stateContext, costLimit = 440000, None ).get diff --git a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala index ee110f90fc..4901e7526b 100644 --- a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala +++ b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala @@ -14,19 +14,21 @@ import org.ergoplatform.wallet.interpreter.{ErgoInterpreter, TransactionHintsBag import org.ergoplatform.wallet.protocol.context.InputContext import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, Input} import org.scalacheck.Gen -import scalan.util.BenchmarkUtil +import sigma.util.BenchmarkUtil import scorex.crypto.authds.ADKey import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 import scorex.util.{ModifierId, bytesToId} +import sigma.Colls import sigmastate.AND import sigmastate.Values.{ByteArrayConstant, ByteConstant, IntConstant, LongArrayConstant, SigmaPropConstant, TrueLeaf} -import sigmastate.basics.CryptoConstants -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.CryptoConstants +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.eval.Extensions._ + import scala.util.{Random, Try} class ErgoTransactionSpec extends ErgoPropertyTest with ErgoTestConstants { diff --git a/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala b/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala index ae85bade3b..7398a9ce5f 100644 --- a/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala +++ b/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala @@ -6,6 +6,7 @@ import org.ergoplatform.utils.ErgoPropertyTest import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, Input} import org.scalatest.Assertion +import sigma.Colls import sigmastate.Values.ShortConstant import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.eval._ diff --git a/src/test/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerSpecification.scala b/src/test/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerSpecification.scala index 6c5055ed5e..2430aa7728 100644 --- a/src/test/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerSpecification.scala +++ b/src/test/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerSpecification.scala @@ -157,7 +157,6 @@ class ErgoNodeViewSynchronizerSpecification extends HistoryTestHelpers with Matc val p: ConnectedPeer = ConnectedPeer( connectionIdGen.sample.get, pchProbe.ref, - lastMessage = 0, Some(peerInfo) ) synchronizerMockRef ! ChangedHistory(history) @@ -197,7 +196,6 @@ class ErgoNodeViewSynchronizerSpecification extends HistoryTestHelpers with Matc val peer: ConnectedPeer = ConnectedPeer( connectionIdGen.sample.get, pchProbe.ref, - lastMessage = 0, Some(peerInfo) ) } diff --git a/src/test/scala/org/ergoplatform/network/ErgoSyncTrackerSpecification.scala b/src/test/scala/org/ergoplatform/network/ErgoSyncTrackerSpecification.scala index 0f9580ce63..b96081117e 100644 --- a/src/test/scala/org/ergoplatform/network/ErgoSyncTrackerSpecification.scala +++ b/src/test/scala/org/ergoplatform/network/ErgoSyncTrackerSpecification.scala @@ -8,9 +8,9 @@ import scorex.core.network.peer.PeerInfo class ErgoSyncTrackerSpecification extends ErgoPropertyTest { property("getters test") { val time = 10L - val peerInfo = PeerInfo(defaultPeerSpec, time, Some(Incoming)) + val peerInfo = PeerInfo(defaultPeerSpec, time, Some(Incoming), 5L) val cid = ConnectionId(inetAddr1, inetAddr2, Incoming) - val connectedPeer = ConnectedPeer(cid, handlerRef = null, lastMessage = 5L, Some(peerInfo)) + val connectedPeer = ConnectedPeer(cid, handlerRef = null, Some(peerInfo)) val syncTracker = ErgoSyncTracker(settings.scorexSettings.network) val height = 1000 diff --git a/src/test/scala/org/ergoplatform/network/HeaderSerializationSpecification.scala b/src/test/scala/org/ergoplatform/network/HeaderSerializationSpecification.scala index 1196482d4c..8270804c48 100644 --- a/src/test/scala/org/ergoplatform/network/HeaderSerializationSpecification.scala +++ b/src/test/scala/org/ergoplatform/network/HeaderSerializationSpecification.scala @@ -9,7 +9,7 @@ import scorex.crypto.authds.ADDigest import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import scorex.util.encode.Base16 -import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.crypto.CryptoConstants.EcPointType import java.nio.ByteBuffer diff --git a/src/test/scala/org/ergoplatform/network/PeerFilteringRuleSpecification.scala b/src/test/scala/org/ergoplatform/network/PeerFilteringRuleSpecification.scala index 028d9af7df..cd7ee1e71e 100644 --- a/src/test/scala/org/ergoplatform/network/PeerFilteringRuleSpecification.scala +++ b/src/test/scala/org/ergoplatform/network/PeerFilteringRuleSpecification.scala @@ -12,8 +12,8 @@ class PeerFilteringRuleSpecification extends ErgoPropertyTest { private def peerWithVersion(version: Version): ConnectedPeer = { val ref = ActorRef.noSender val peerSpec = PeerSpec("", version, "", None, Seq.empty) - val peerInfo = PeerInfo(peerSpec, lastHandshake = 0L, None) - ConnectedPeer(ConnectionId(null, null, null), ref, lastMessage = 0L, Some(peerInfo)) + val peerInfo = PeerInfo(peerSpec, lastHandshake = 0L, None, 0L) + ConnectedPeer(ConnectionId(null, null, null), ref, Some(peerInfo)) } property("syncv2 filter") { diff --git a/src/test/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexerSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexerSpecification.scala index c26f9cf451..a377e8a2e7 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexerSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/extra/ExtraIndexerSpecification.scala @@ -11,17 +11,18 @@ import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.nodeView.history.ErgoHistory -import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.{boxSegmentId, hashErgoTree, txSegmentId} +import org.ergoplatform.nodeView.history.extra.IndexedErgoAddressSerializer.hashErgoTree +import org.ergoplatform.nodeView.history.extra.SegmentSerializer.{boxSegmentId, txSegmentId} import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption import org.ergoplatform.nodeView.state._ import org.ergoplatform.settings.{ErgoSettings, NetworkType, NipopowSettings, NodeConfigurationSettings, UtxoSettings} import org.ergoplatform.utils.{ErgoPropertyTest, ErgoTestHelpers, HistoryTestHelpers} import scorex.util.{ModifierId, bytesToId} import sigmastate.Values -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.Extensions._ import sigmastate.eval._ -import special.collection.Coll +import sigma.{Coll, Colls} import spire.implicits.cfor import java.io.File @@ -29,17 +30,21 @@ import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.reflect.ClassTag import scala.util.{Random, Try} class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase with HistoryTestHelpers { + type ID_LL = mutable.HashMap[ModifierId,(Long,Long)] + override protected val saveLimit: Int = 1 // save every block override protected implicit val segmentTreshold: Int = 8 // split to smaller segments override protected implicit val addressEncoder: ErgoAddressEncoder = initSettings.chainSettings.addressEncoder val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, - -1, UtxoSettings(false, 0, 2), NipopowSettings(false, 1), mining = false, ChainGenerator.txCostLimit, ChainGenerator.txSizeLimit, useExternalMiner = false, - internalMinersCount = 1, internalMinerPollingInterval = 1.second, miningPubKeyHex = None, offlineGeneration = false, + -1, UtxoSettings(utxoBootstrap = false, 0, 2), NipopowSettings(nipopowBootstrap = false, 1), mining = false, + ChainGenerator.txCostLimit, ChainGenerator.txSizeLimit, useExternalMiner = false, internalMinersCount = 1, + internalMinerPollingInterval = 1.second, miningPubKeyHex = None, offlineGeneration = false, 200, 5.minutes, 100000, 1.minute, mempoolSorting = SortingOption.FeePerByte, rebroadcastCount = 20, 1000000, 100, adProofsSuffixLength = 112 * 1024, extraIndex = false) @@ -67,76 +72,102 @@ class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase w general.clear() boxes.clear() trees.clear() + tokens.clear() + segments.clear() } - def getAddresses(limit: Int): (mutable.HashMap[ModifierId,(Long,Long)],Int,Int) = { + def manualIndex(limit: Int): (ID_LL, // address -> (erg,tokenSum) + ID_LL, // tokenId -> (boxesCount,_) + Int, // txs indexed + Int) = { // boxes indexed var txsIndexed = 0 var boxesIndexed = 0 - val addresses: mutable.HashMap[ModifierId,(Long,Long)] = mutable.HashMap[ModifierId,(Long,Long)]() + val addresses: ID_LL = mutable.HashMap[ModifierId,(Long,Long)]() + val indexedTokens: ID_LL = mutable.HashMap[ModifierId,(Long,Long)]() cfor(1)(_ <= limit, _ + 1) { i => - _history.getReader.bestBlockTransactionsAt(i).get.txs.foreach(tx => { + _history.getReader.bestBlockTransactionsAt(i).get.txs.foreach { tx => txsIndexed += 1 if (i != 1) { - tx.inputs.foreach(input => { + tx.inputs.foreach { input => val iEb: IndexedErgoBox = _history.getReader.typedExtraIndexById[IndexedErgoBox](bytesToId(input.boxId)).get val address = hashErgoTree(ExtraIndexer.getAddress(iEb.box.ergoTree)(addressEncoder).script) val prev = addresses(address) addresses.put(address, (prev._1 - iEb.box.value, prev._2 - iEb.box.additionalTokens.toArray.map(_._2).sum)) - }) + } } - tx.outputs.foreach(output => { + tx.outputs.foreach { output => boxesIndexed += 1 val address = hashErgoTree(addressEncoder.fromProposition(output.ergoTree).get.script) val prev = addresses.getOrElse(address, (0L, 0L)) addresses.put(address, (prev._1 + output.value, prev._2 + output.additionalTokens.toArray.map(_._2).sum)) - }) - }) + cfor(0)(_ < output.additionalTokens.length, _ + 1) { j => + val token = IndexedToken.fromBox(new IndexedErgoBox(i, None, None, output, 0), j) + val prev2 = indexedTokens.getOrElse(token.id, (0L, 0L)) + indexedTokens.put(token.id, (prev2._1 + 1, 0)) + } + } + } } - (addresses, txsIndexed, boxesIndexed) + (addresses, indexedTokens, txsIndexed, boxesIndexed) } - def checkAddresses(addresses: mutable.HashMap[ModifierId,(Long,Long)], isSegment: Boolean = false): Int = { - var mismatches: Int = 0 - addresses.foreach(addr => { - history.typedExtraIndexById[IndexedErgoAddress](addr._1) match { - case Some(iEa) => - if(!isSegment && (iEa.balanceInfo.get.nanoErgs != addr._2._1 || iEa.balanceInfo.get.tokens.map(_._2).sum != addr._2._2)) - mismatches += 1 - if(!isSegment) { + def checkSegmentables[T <: Segment[_] : ClassTag](segmentables: ID_LL, + isChild: Boolean = false, + check: ((T, (Long, Long))) => Boolean + ): Int = { + var errors: Int = 0 + segmentables.foreach { segmentable => + history.typedExtraIndexById[T](segmentable._1) match { + case Some(obj: T) => + if(isChild) { // this is a segment // check tx segments - val txSegments: mutable.HashMap[ModifierId,(Long,Long)] = mutable.HashMap.empty[ModifierId, (Long,Long)] - txSegments ++= (0 until iEa.txSegmentCount).map(txSegmentId(iEa.treeHash, _)).map(Tuple2(_, (0L, 0L))) - checkAddresses(txSegments, isSegment = true) shouldBe 0 + val txSegments: ID_LL = mutable.HashMap.empty[ModifierId,(Long,Long)] + txSegments ++= (0 until obj.txSegmentCount).map(n => obj.idMod(txSegmentId(obj.parentId, n))).map(Tuple2(_, (0L, 0L))) + checkSegmentables(txSegments, isChild = true, check) shouldBe 0 // check box segments - val boxSegments: mutable.HashMap[ModifierId,(Long,Long)] = mutable.HashMap.empty[ModifierId,(Long,Long)] - boxSegments ++= (0 until iEa.boxSegmentCount).map(boxSegmentId(iEa.treeHash, _)).map(Tuple2(_, (0L, 0L))) - checkAddresses(boxSegments, isSegment = true) shouldBe 0 - } - // check boxes in memory - iEa.boxes.foreach(boxNum => - NumericBoxIndex.getBoxByNumber(history, boxNum) match { - case Some(iEb) => - if (iEb.isSpent) - boxNum.toInt should be <= 0 - else - boxNum.toInt should be >= 0 - case None => System.err.println(s"Box $boxNum not found in database") + val boxSegments: ID_LL = mutable.HashMap.empty[ModifierId,(Long,Long)] + boxSegments ++= (0 until obj.boxSegmentCount).map(n => obj.idMod(boxSegmentId(obj.parentId, n))).map(Tuple2(_, (0L, 0L))) + checkSegmentables(boxSegments, isChild = true, check) shouldBe 0 + }else { // this is the parent object + // check properties of object + if(!check((obj, segmentable._2))) + errors += 1 + // check boxes in memory + obj.boxes.foreach { boxNum => + NumericBoxIndex.getBoxByNumber(history, boxNum) match { + case Some(iEb) => + if (iEb.isSpent) + boxNum.toInt should be <= 0 + else + boxNum.toInt should be >= 0 + case None => + System.err.println(s"Box $boxNum not found in database") + errors += 1 + } + } + // check txs in memory + obj.txs.foreach { txNum => + NumericTxIndex.getTxByNumber(history, txNum) shouldNot be(empty) } - ) - // check txs in memory - iEa.txs.foreach(txNum => - NumericTxIndex.getTxByNumber(history, txNum) shouldNot be(empty) - ) - case None => - if (addr._2._1 != 0L && addr._2._2 != 0L) { - mismatches += 1 - System.err.println(s"Address ${addr._1} should exist, but was not found") } + case None => + System.err.println(s"Segmentable object ${segmentable._1} should exist, but was not found") + errors += 1 } - }) - mismatches + } + errors } + def checkAddresses(addresses: ID_LL): Int = + checkSegmentables[IndexedErgoAddress](addresses, isChild = false, seg => { + seg._1.balanceInfo.get.nanoErgs == seg._2._1 && seg._1.balanceInfo.get.tokens.map(_._2).sum == seg._2._2 + }) + + def checkTokens(indexedTokens: ID_LL): Int = + checkSegmentables[IndexedToken](indexedTokens, isChild = false, seg => { + seg._1.boxCount == seg._2._1 + }) + property("extra indexer transactions") { createDB() run() @@ -160,10 +191,17 @@ class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase w property("extra indexer addresses") { createDB() run() - val (addresses, _, _) = getAddresses(HEIGHT) + val (addresses, _, _, _) = manualIndex(HEIGHT) checkAddresses(addresses) shouldBe 0 } + property("extra indexer tokens") { + createDB() + run() + val (_, indexedTokens, _, _) = manualIndex(HEIGHT) + checkTokens(indexedTokens) shouldBe 0 + } + property("extra indexer rollback") { createDB() @@ -173,7 +211,7 @@ class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase w val boxIndexBefore = globalBoxIndex // manually count balances - val (addresses, txsIndexed, boxesIndexed) = getAddresses(BRANCHPOINT) + val (addresses, indexedTokens, txsIndexed, boxesIndexed) = manualIndex(BRANCHPOINT) // perform rollback removeAfter(BRANCHPOINT) @@ -181,6 +219,9 @@ class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase w // address balances checkAddresses(addresses) shouldBe 0 + // token indexes + checkTokens(indexedTokens) shouldBe 0 + // check indexnumbers globalTxIndex shouldBe txsIndexed globalBoxIndex shouldBe boxesIndexed @@ -208,8 +249,9 @@ class ExtraIndexerSpecification extends ErgoPropertyTest with ExtraIndexerBase w run() // Check addresses again - val (addresses2, _, _) = getAddresses(HEIGHT) + val (addresses2, indexedTokens2, _, _) = manualIndex(HEIGHT) checkAddresses(addresses2) shouldBe 0 + checkTokens(indexedTokens2) shouldBe 0 // check indexnumbers again globalTxIndex shouldBe txIndexBefore diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index d08408e7cf..38aaa799d3 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -72,7 +72,8 @@ class ErgoMemPoolSpec extends AnyFlatSpec var poolCost = ErgoMemPool.empty(sortByCostSettings) poolCost = poolCost.process(UnconfirmedTransaction(tx, None), wus)._1 - val cost = wus.validateWithCost(tx, Int.MaxValue).get + val validationContext = wus.stateContext.simplifiedUpcoming() + val cost = wus.validateWithCost(tx, validationContext, Int.MaxValue, None).get poolCost.pool.orderedTransactions.firstKey.weight shouldBe OrderedTxPool.weighted(tx, cost).weight } diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala index ce643b96ef..7f2f6b7cca 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala @@ -9,8 +9,8 @@ import org.ergoplatform.{ErgoBox, ErgoTreePredef, Height, Self} import scorex.crypto.authds.avltree.batch.Remove import sigmastate.Values._ import sigmastate._ -import sigmastate.basics.CryptoConstants.dlogGroup -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.CryptoConstants.dlogGroup +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.{CompiletimeIRContext, IRContext} import sigmastate.lang.Terms._ import sigmastate.lang.{CompilerSettings, SigmaCompiler, TransformingSigmaBuilder} diff --git a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala index a0cd911b10..e3dc146a8c 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala @@ -15,13 +15,13 @@ import org.ergoplatform.settings.Constants import org.ergoplatform.utils.{ErgoPropertyTest, RandomWrapper} import org.ergoplatform.utils.generators.ErgoTransactionGenerators import scorex.core._ -import scorex.core.transaction.state.TransactionValidation.TooHighCostError +import scorex.core.transaction.TooHighCostError import scorex.crypto.authds.ADKey import scorex.db.ByteArrayWrapper import scorex.util.{ModifierId, bytesToId} import scorex.util.encode.Base16 import sigmastate.Values.ByteArrayConstant -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.interpreter.ProverResult import sigmastate.helpers.TestingHelpers._ @@ -51,7 +51,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val unsignedTx = new UnsignedErgoTransaction(inputs, IndexedSeq(), newBoxes) val tx: ErgoTransaction = ErgoTransaction(defaultProver.sign(unsignedTx, IndexedSeq(foundersBox), emptyDataBoxes, us.stateContext).get) val txCostLimit = initSettings.nodeSettings.maxTransactionCost - us.validateWithCost(tx, None, txCostLimit, None).get should be <= 100000 + us.validateWithCost(tx, us.stateContext.simplifiedUpcoming(), txCostLimit, None).get should be <= 100000 val block1 = validFullBlock(Some(lastBlock), us, Seq(ErgoTransaction(tx))) us = us.applyModifier(block1, None)(_ => ()).get foundersBox = tx.outputs.head @@ -94,17 +94,18 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera ) val unsignedTx = new UnsignedErgoTransaction(inputs, IndexedSeq(), newBoxes) val tx = ErgoTransaction(defaultProver.sign(unsignedTx, IndexedSeq(foundersBox), emptyDataBoxes, us.stateContext).get) - val validationRes1 = us.validateWithCost(tx, 100000) + val validationContext = us.stateContext.simplifiedUpcoming() + val validationRes1 = us.validateWithCost(tx, validationContext, 100000, None) validationRes1 shouldBe 'success val txCost = validationRes1.get - val validationRes2 = us.validateWithCost(tx, txCost - 1) + val validationRes2 = us.validateWithCost(tx, validationContext, txCost - 1, None) validationRes2 shouldBe 'failure validationRes2.toEither.left.get.isInstanceOf[TooHighCostError] shouldBe true - us.validateWithCost(tx, txCost + 1) shouldBe 'success + us.validateWithCost(tx, validationContext, txCost + 1, None) shouldBe 'success - us.validateWithCost(tx, txCost) shouldBe 'success + us.validateWithCost(tx, validationContext, txCost, None) shouldBe 'success height = height + 1 } diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala index 9659837cb8..5f5967d4d3 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala @@ -17,7 +17,7 @@ import org.scalacheck.Gen import org.scalatest.concurrent.Eventually import scorex.util.ModifierId import scorex.util.encode.Base16 -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.crypto.DLogProtocol.DLogProverInput import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.{CAND, CTHRESHOLD} diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistryBenchmark.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistryBenchmark.scala index 09639adbb7..1dad6a9df8 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistryBenchmark.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistryBenchmark.scala @@ -47,7 +47,7 @@ object WalletRegistryBenchmark extends App with ErgoTestConstants { } val prover = ErgoProvingInterpreter(rootSecret +: derivedSecrets, parameters) - var walletVars = WalletVars.apply(storage, settings).withProver(prover) + val walletVars = WalletVars.apply(storage, settings).withProver(prover) val boxes = walletVars.proverOpt.get.hdPubKeys.map { pk => createBox(1000000000, pk.key, 1) diff --git a/src/test/scala/org/ergoplatform/reemission/ReemissionRulesSpec.scala b/src/test/scala/org/ergoplatform/reemission/ReemissionRulesSpec.scala index 2d5eca8bf7..46a670cab6 100644 --- a/src/test/scala/org/ergoplatform/reemission/ReemissionRulesSpec.scala +++ b/src/test/scala/org/ergoplatform/reemission/ReemissionRulesSpec.scala @@ -5,9 +5,10 @@ import org.ergoplatform.settings.{MonetarySettings, ReemissionSettings} import org.ergoplatform.utils.{ErgoPropertyTest, ErgoTestConstants} import scorex.crypto.hash.Blake2b256 import scorex.util.ModifierId +import sigma.Colls import sigmastate.AvlTreeData import sigmastate.TrivialProp.TrueProp -import sigmastate.eval.{Colls, Digest32Coll} +import sigmastate.eval.Digest32Coll import sigmastate.helpers.TestingHelpers.testBox import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter} import sigmastate.interpreter.Interpreter.emptyEnv diff --git a/src/test/scala/org/ergoplatform/sanity/ErgoSanityDigest.scala b/src/test/scala/org/ergoplatform/sanity/ErgoSanityDigest.scala index 674f851227..d9b07a50e7 100644 --- a/src/test/scala/org/ergoplatform/sanity/ErgoSanityDigest.scala +++ b/src/test/scala/org/ergoplatform/sanity/ErgoSanityDigest.scala @@ -84,13 +84,12 @@ class ErgoSanityDigest extends ErgoSanity[DIGEST_ST] { val tx = validErgoTransactionGenTemplate(minAssets = 0, maxAssets = 0).sample.get._2 - val peerInfo = PeerInfo(defaultPeerSpec, Long.MaxValue) + val peerInfo = PeerInfo(defaultPeerSpec, Long.MaxValue, None, 0L) @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) val p: ConnectedPeer = ConnectedPeer( connectionIdGen.sample.get, pchProbe.ref, - lastMessage = 0, Some(peerInfo) ) ref ! ChangedHistory(h) diff --git a/src/test/scala/org/ergoplatform/sanity/ErgoSanityUTXO.scala b/src/test/scala/org/ergoplatform/sanity/ErgoSanityUTXO.scala index 52cb2fbaab..ef6107e35b 100644 --- a/src/test/scala/org/ergoplatform/sanity/ErgoSanityUTXO.scala +++ b/src/test/scala/org/ergoplatform/sanity/ErgoSanityUTXO.scala @@ -78,12 +78,11 @@ class ErgoSanityUTXO extends ErgoSanity[UTXO_ST] with ErgoTestHelpers { val tx = validErgoTransactionGenTemplate(minAssets = 0, maxAssets = 0).sample.get._2 - val peerInfo = PeerInfo(defaultPeerSpec, System.currentTimeMillis()) + val peerInfo = PeerInfo(defaultPeerSpec, System.currentTimeMillis(), None, 0L) @SuppressWarnings(Array("org.wartremover.warts.OptionPartial")) val p: ConnectedPeer = ConnectedPeer( connectionIdGen.sample.get, pchProbe.ref, - lastMessage = 0, Some(peerInfo) ) ref ! ChangedHistory(h) diff --git a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala index 571586ca26..016696d708 100644 --- a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala +++ b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala @@ -3,7 +3,7 @@ package org.ergoplatform.serialization import io.circe.syntax._ import io.circe.{ACursor, Decoder, Encoder, Json} import org.ergoplatform.ErgoBox -import org.ergoplatform.ErgoBox.NonMandatoryRegisterId +import org.ergoplatform.ErgoBox.{AdditionalRegisters, NonMandatoryRegisterId} import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.http.api.ApiEncoderOption.HideDetails.implicitValue import org.ergoplatform.http.api.ApiEncoderOption.{Detalization, ShowDetails} @@ -187,7 +187,7 @@ class JsonSerializationSpec extends ErgoPropertyTest with WalletGenerators with stringify(decodedAssets) should contain theSameElementsAs stringify(assets) } - private def checkRegisters(c: ACursor, registers: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]]) = { + private def checkRegisters(c: ACursor, registers: AdditionalRegisters) = { val Right(decodedRegs) = c.as[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] decodedRegs should contain theSameElementsAs registers } diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 6db85c433d..7fb07b62f9 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -16,7 +16,7 @@ import org.ergoplatform.settings._ import org.ergoplatform.utils.{ErgoTestHelpers, HistoryTestHelpers} import org.ergoplatform.wallet.boxes.{BoxSelector, ReplaceCompactCollectBoxSelector} import scorex.util.ModifierId -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import java.io.File import scala.annotation.tailrec diff --git a/src/test/scala/org/ergoplatform/tools/FeeSimulator.scala b/src/test/scala/org/ergoplatform/tools/FeeSimulator.scala index c09e9c6bd9..9eebe3f4b7 100644 --- a/src/test/scala/org/ergoplatform/tools/FeeSimulator.scala +++ b/src/test/scala/org/ergoplatform/tools/FeeSimulator.scala @@ -6,7 +6,8 @@ import org.ergoplatform.settings.LaunchParameters._ import org.ergoplatform.{ErgoBoxCandidate, Input} import scorex.crypto.authds.ADKey import scorex.utils.Random -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigma.Colls +import sigmastate.crypto.DLogProtocol.DLogProverInput import sigmastate.eval.Extensions.ArrayByteOps import sigmastate.eval._ import sigmastate.interpreter.{ContextExtension, ProverResult} diff --git a/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala b/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala index cd24b2d52e..51d7e80597 100644 --- a/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala +++ b/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala @@ -22,8 +22,8 @@ import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 import scorex.util.ScorexLogging import sigmastate.Values.ErgoTree -import sigmastate.basics.CryptoConstants.EcPointType -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.CryptoConstants.EcPointType +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.interpreter.{ContextExtension, ProverResult} import scala.concurrent.duration._ diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 078719ccb5..b68886a573 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -42,7 +42,7 @@ import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 import scorex.db.ByteArrayWrapper import scorex.util.Random -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} import scala.collection.mutable import scala.concurrent.duration._ diff --git a/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala b/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala index b76ac7485b..42407311f2 100644 --- a/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala +++ b/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala @@ -16,12 +16,13 @@ import org.ergoplatform.utils.fixtures.WalletFixture import scorex.crypto.authds.ADKey import scorex.crypto.hash.Blake2b256 import scorex.util.ModifierId +import sigma.Colls import sigmastate.Values.ErgoTree -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.interpreter.ProverResult -import special.collection.Extensions._ +import sigma.Extensions._ trait WalletTestOps extends NodeViewBaseOps { diff --git a/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala b/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala index 441bd039f2..cfe35ca205 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala @@ -7,12 +7,13 @@ import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionCandida import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.history.popow.{NipopowAlgos, PoPowHeader} import org.ergoplatform.modifiers.mempool.ErgoTransaction -import org.ergoplatform.modifiers.{NonHeaderBlockSection, ErgoFullBlock, BlockSection} +import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock, NonHeaderBlockSection} import org.ergoplatform.nodeView.history.ErgoHistory import org.ergoplatform.settings.Constants import org.ergoplatform.utils.{BoxUtils, ErgoTestConstants} import scorex.crypto.authds.{ADKey, SerializedAdProof} import scorex.crypto.hash.Digest32 +import sigma.Colls import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.{ContextExtension, ProverResult} diff --git a/src/test/scala/org/ergoplatform/utils/generators/ErgoGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/ErgoGenerators.scala index 9c0eed8a88..ad49b7557a 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ErgoGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ErgoGenerators.scala @@ -23,9 +23,9 @@ import scorex.crypto.authds.{ADDigest, SerializedAdProof} import scorex.crypto.hash.Digest32 import scorex.testkit.generators.CoreGenerators import sigmastate.Values.ErgoTree -import sigmastate.basics.CryptoConstants.EcPointType -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} -import sigmastate.basics.{CryptoConstants, DiffieHellmanTupleProverInput, ProveDHTuple} +import sigmastate.crypto.CryptoConstants.EcPointType +import sigmastate.crypto.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.crypto.{CryptoConstants, DiffieHellmanTupleProverInput, ProveDHTuple} import sigmastate.interpreter.ProverResult import scala.util.Random diff --git a/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala index 7630d2cdbe..978617e43d 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala @@ -24,7 +24,7 @@ import scorex.crypto.hash.Blake2b256 import scorex.db.ByteArrayWrapper import scorex.util.encode.Base16 import sigmastate.Values.ErgoTree -import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.crypto.DLogProtocol.ProveDlog import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ diff --git a/src/test/scala/scorex/testkit/generators/ObjectGenerators.scala b/src/test/scala/scorex/testkit/generators/ObjectGenerators.scala index 7c9dddf961..f99112fab7 100644 --- a/src/test/scala/scorex/testkit/generators/ObjectGenerators.scala +++ b/src/test/scala/scorex/testkit/generators/ObjectGenerators.scala @@ -100,12 +100,12 @@ trait ObjectGenerators { def peerInfoGen: Gen[PeerInfo] = for { peerSpec <- peerSpecGen - } yield PeerInfo(peerSpec, 0L, Some(Incoming)) + } yield PeerInfo(peerSpec, 0L, Some(Incoming), 0L) def connectedPeerGen(peerRef: ActorRef): Gen[ConnectedPeer] = for { connectionId <- connectionIdGen peerInfo <- peerInfoGen - } yield ConnectedPeer(connectionId, peerRef, 0, Some(peerInfo)) + } yield ConnectedPeer(connectionId, peerRef, Some(peerInfo)) def peerSpecGen: Gen[PeerSpec] = for { declaredAddress <- Gen.frequency(5 -> const(None), 5 -> some(inetSocketAddressGen))