Skip to content

Commit

Permalink
Merge pull request #1 from ergoplatform/master
Browse files Browse the repository at this point in the history
merge latest
  • Loading branch information
reqlez authored Nov 7, 2023
2 parents d553381 + b6bef0b commit d5ae595
Show file tree
Hide file tree
Showing 17 changed files with 470 additions and 33 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ lazy val commonSettings = Seq(
scalacOptions ++= commonScalacOptions,
scalaVersion := "2.12.15",
organization := "org.ergoplatform",
version := "9.17.4",
version := "9.17.5",
resolvers += Resolver.sonatypeRepo("public"),
resolvers += Resolver.sonatypeRepo("snapshots"),
libraryDependencies ++= dependencies.Testing ++ dependencies.CompilerPlugins,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.http4s.client.Client
import pureconfig.generic.auto._
import tofu.concurrent.MakeRef
import tofu.logging.Logs
import cats.effect.IO

import scala.concurrent.ExecutionContext.global

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.ergoplatform.explorer.protocol.constants
import org.ergoplatform.explorer.protocol.models.ApiFullBlock
import org.ergoplatform.explorer.settings.ProtocolSettings
import org.ergoplatform.explorer.{Address, BuildFrom, CRaise}
import org.ergoplatform.{ErgoAddressEncoder, ErgoScriptPredef, Pay2SAddress}
import org.ergoplatform.{ErgoScriptPredef, Pay2SAddress}
import scorex.util.encode.Base16
import sigmastate.basics.DLogProtocol.ProveDlog
import sigmastate.interpreter.CryptoConstants.EcPointType
Expand Down Expand Up @@ -106,26 +106,13 @@ final class BlockInfoBuildFrom[
private def minerRewardAndFee(
apiBlock: ApiFullBlock
)(protocolSettings: ProtocolSettings): (Long, Long) = {
val emission = protocolSettings.emission.emissionAtHeight(apiBlock.header.height.toLong)
val emission = protocolSettings.emission.emissionAt(apiBlock.header.height.toLong)
val reward = math.min(constants.TeamTreasuryThreshold, emission)
val eip27Reward =
if (reward >= constants.Eip27UpperPoint) reward - constants.Eip27DefaultReEmission
else if (constants.Eip27LowerPoint < reward) reward - (reward - constants.Eip27ResidualEmission)
else reward
val fee = apiBlock.transactions.transactions
.flatMap(_.outputs.toList)
.filter(_.ergoTree.unwrapped == constants.FeePropositionScriptHex)
.map(_.value)
.sum
protocolSettings.networkPrefix.value.toByte match {
case ErgoAddressEncoder.MainnetNetworkPrefix
if apiBlock.header.height >= constants.MainnetEip27ActivationHeight =>
(eip27Reward, fee)
case ErgoAddressEncoder.TestnetNetworkPrefix
if apiBlock.header.height >= constants.TestnetEip27ActivationHeight =>
(eip27Reward, fee)
case _ =>
(reward, fee)
}
(reward, fee)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package org.ergoplatform.explorer.http.api.models
import io.circe.Codec
import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder
import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder
import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder
import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder
import org.ergoplatform.explorer.db.models.aggregates.{ExtendedAsset, ExtendedUAsset}
import org.ergoplatform.explorer.db.models.aggregates.{AnyAsset, ExtendedAsset, ExtendedUAsset}
import org.ergoplatform.explorer.{TokenId, TokenType}
import sttp.tapir.{Schema, Validator}

Expand All @@ -26,6 +24,9 @@ object AssetInstanceInfo {
def apply(asset: ExtendedAsset): AssetInstanceInfo =
AssetInstanceInfo(asset.tokenId, asset.index, asset.amount, asset.name, asset.decimals, asset.`type`)

def apply(asset: AnyAsset): AssetInstanceInfo =
AssetInstanceInfo(asset.tokenId, asset.index, asset.amount, asset.name, asset.decimals, asset.`type`)

implicit val codec: Codec[AssetInstanceInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder)

implicit val schema: Schema[AssetInstanceInfo] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import org.ergoplatform.explorer.http.api.ApiErr
import org.ergoplatform.explorer.http.api.commonDirectives._
import org.ergoplatform.explorer.http.api.models.Sorting.SortOrder
import org.ergoplatform.explorer.http.api.models.{HeightRange, Items, Paging}
import org.ergoplatform.explorer.http.api.v1.models.{BoxAssetsQuery, BoxQuery, MOutputInfo, OutputInfo}
import org.ergoplatform.explorer.http.api.v1.models.{
AnyOutputInfo,
BoxAssetsQuery,
BoxQuery,
MOutputInfo,
OutputInfo
}
import org.ergoplatform.explorer.settings.RequestsSettings
import sttp.capabilities.fs2.Fs2Streams
import sttp.tapir._
Expand Down Expand Up @@ -170,4 +176,11 @@ final class BoxesEndpointDefs[F[_]](settings: RequestsSettings) {
"Search among UTXO set by ergoTreeTemplateHash and tokens. " +
"The resulted UTXOs will contain at lest one of the given tokens."
)

def getAllUnspentOutputsByAddressDef: Endpoint[(Address, Paging, SortOrder), ApiErr, Items[AnyOutputInfo], Any] =
baseEndpointDef.get
.in(PathPrefix / "unspent" / "all" / "byAddress" / path[Address])
.in(paging(settings.maxEntitiesPerRequest))
.in(ordering)
.out(jsonBody[Items[AnyOutputInfo]])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.ergoplatform.explorer.http.api.v1.models

import derevo.circe.{decoder, encoder}
import derevo.derive
import io.circe.Json
import org.ergoplatform.explorer._
import org.ergoplatform.explorer.db.models.AnyOutput
import org.ergoplatform.explorer.db.models.aggregates.AnyAsset
import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo
import sttp.tapir.{Schema, SchemaType, Validator}

@derive(encoder, decoder)
final case class AnyOutputInfo(
boxId: BoxId,
transactionId: TxId,
headerId: Option[BlockId],
value: Long,
index: Int,
globalIndex: Option[Long],
creationHeight: Int,
settlementHeight: Option[Int],
ergoTree: HexString,
ergoTreeConstants: String,
ergoTreeScript: String,
address: Address,
assets: List[AssetInstanceInfo],
additionalRegisters: Json,
spentTransactionId: Option[TxId],
mainChain: Option[Boolean]
)

object AnyOutputInfo {

implicit val schema: Schema[AnyOutputInfo] =
Schema
.derived[AnyOutputInfo]
.modify(_.boxId)(_.description("Id of the box"))
.modify(_.transactionId)(_.description("Id of the transaction that created the box"))
.modify(_.headerId)(_.description("Id of the block a box included in"))
.modify(_.value)(_.description("Value of the box in nanoERG"))
.modify(_.index)(_.description("Index of the output in a transaction"))
.modify(_.globalIndex)(_.description("Global index of the output in the blockchain"))
.modify(_.creationHeight)(_.description("Height at which the box was created"))
.modify(_.settlementHeight)(_.description("Height at which the box got fixed in blockchain"))
.modify(_.ergoTree)(_.description("Serialized ergo tree"))
.modify(_.address)(_.description("An address derived from ergo tree"))
.modify(_.spentTransactionId)(_.description("Id of the transaction this output was spent by"))

implicit val validator: Validator[AnyOutputInfo] = schema.validator

implicit private def registersSchema: Schema[Json] =
Schema(
SchemaType.SOpenProduct(
Schema(SchemaType.SString[Json]())
)(_ => Map.empty)
)

def apply(
o: AnyOutput,
assets: List[AnyAsset]
): AnyOutputInfo = {
val (ergoTreeConstants, ergoTreeScript) = PrettyErgoTree
.fromHexString(o.ergoTree)
.fold(
_ => ("", ""),
tree => (tree.constants, tree.script)
)
AnyOutputInfo(
o.boxId,
o.txId,
o.headerId,
o.value,
o.index,
o.globalIndex,
o.creationHeight,
o.settlementHeight,
o.ergoTree,
ergoTreeConstants,
ergoTreeScript,
o.address,
assets.sortBy(_.index).map(AssetInstanceInfo(_)),
o.additionalRegisters,
o.spendingTxId,
o.mainChain
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ final class BoxesRoutes[
getOutputsByAddressR <+>
getUnspentOutputsByAddressR <+>
getOutputByIdR <+>
`getUnspent&UnconfirmedOutputsMergedByAddressR`
`getUnspent&UnconfirmedOutputsMergedByAddressR` <+>
getAllUnspentOutputsR

private def interpreter = Http4sServerInterpreter(opts)

Expand Down Expand Up @@ -150,6 +151,11 @@ final class BoxesRoutes[
interpreter.toRoutes(defs.searchUnspentOutputsByTokensUnionDef) { case (query, paging) =>
service.searchUnspentByAssetsUnion(query, paging).adaptThrowable.value
}

private def getAllUnspentOutputsR: HttpRoutes[F] =
interpreter.toRoutes(defs.getAllUnspentOutputsByAddressDef) { case (address, paging, ord) =>
service.getAllUnspentOutputs(address, paging, ord).adaptThrowable.value
}
}

object BoxesRoutes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,27 @@ import org.ergoplatform.explorer.Err.{RefinementFailed, RequestProcessingErr}
import org.ergoplatform.explorer._
import org.ergoplatform.explorer.db.Trans
import org.ergoplatform.explorer.db.algebra.LiftConnectionIO
import org.ergoplatform.explorer.db.models.Output
import org.ergoplatform.explorer.db.models.aggregates.ExtendedOutput
import org.ergoplatform.explorer.db.repositories.{AssetRepo, HeaderRepo, OutputRepo, UInputRepo, UOutputRepo}
import org.ergoplatform.explorer.db.models.{AnyOutput, Output, UOutput}
import org.ergoplatform.explorer.db.models.aggregates.{ExtendedOutput, ExtendedUOutput}
import org.ergoplatform.explorer.db.repositories.{
AssetRepo,
HeaderRepo,
OutputRepo,
UAssetRepo,
UInputRepo,
UOutputRepo
}
import org.ergoplatform.explorer.http.api.models.Sorting.SortOrder
import org.ergoplatform.explorer.http.api.models.{HeightRange, Items, Paging}
import org.ergoplatform.explorer.http.api.streaming.CompileStream
import org.ergoplatform.explorer.http.api.v1.models.{BoxAssetsQuery, BoxQuery, MOutputInfo, OutputInfo, UOutputInfo}
import org.ergoplatform.explorer.http.api.v1.models.{
AnyOutputInfo,
BoxAssetsQuery,
BoxQuery,
MOutputInfo,
OutputInfo,
UOutputInfo
}
import org.ergoplatform.explorer.http.api.v1.shared.MempoolProps
import org.ergoplatform.explorer.protocol.sigma._
import org.ergoplatform.explorer.settings.ServiceSettings
Expand Down Expand Up @@ -109,6 +123,10 @@ trait Boxes[F[_]] {
/** Get unspent outputs matching a given `boxQuery`.
*/
def searchUnspentByAssetsUnion(boxQuery: BoxAssetsQuery, paging: Paging): F[Items[OutputInfo]]

/** Get both confirmed & unconfirmed outputs with the given `address` in proposition.
*/
def getAllUnspentOutputs(address: Address, paging: Paging, ord: SortOrder): F[Items[AnyOutputInfo]]
}

object Boxes {
Expand All @@ -119,8 +137,8 @@ object Boxes {
](serviceSettings: ServiceSettings, memprops: MempoolProps[F, D])(trans: D Trans F)(implicit
e: ErgoAddressEncoder
): F[Boxes[F]] =
(HeaderRepo[F, D], OutputRepo[F, D], AssetRepo[F, D], UOutputRepo[F, D], UInputRepo[F, D]).mapN(
new Live(serviceSettings, memprops, _, _, _, _, _)(trans)
(HeaderRepo[F, D], OutputRepo[F, D], AssetRepo[F, D], UAssetRepo[F, D], UOutputRepo[F, D], UInputRepo[F, D]).mapN(
new Live(serviceSettings, memprops, _, _, _, _, _, _)(trans)
)

final private class Live[
Expand All @@ -132,6 +150,7 @@ object Boxes {
headers: HeaderRepo[D, Stream],
outputs: OutputRepo[D, Stream],
assets: AssetRepo[D, Stream],
uassets: UAssetRepo[D],
uoutputs: UOutputRepo[D, Stream],
uinputs: UInputRepo[D, Stream]
)(trans: D Trans F)(implicit e: ErgoAddressEncoder)
Expand Down Expand Up @@ -379,6 +398,18 @@ object Boxes {
.thrushK(trans.xa)
}

def getAllUnspentOutputs(address: Address, paging: Paging, ord: SortOrder): F[Items[AnyOutputInfo]] = {
val ergoTree = addressToErgoTreeHex(address)
(for {
nUnspent <- uoutputs.countAllByErgoTree(ergoTree)
boxes <- uoutputs
.streamAllUnspentByErgoTree(ergoTree, paging.offset, paging.limit, ord.value)
.chunkN(serviceSettings.chunkSize)
.through(toAnyOutputInfo)
.to[List]
} yield Items(boxes, nUnspent)).thrushK(trans.xa)
}

private def toOutputInfo: Pipe[D, Chunk[ExtendedOutput], OutputInfo] =
for {
outs <- _
Expand All @@ -388,6 +419,15 @@ object Boxes {
flattened <- Stream.emits(outsInfo.toList)
} yield flattened

private def toAnyOutputInfo: Pipe[D, Chunk[AnyOutput], AnyOutputInfo] =
for {
outs <- _
outIds <- Stream.emit(outs.toList.map(_.boxId).toNel).unNone
assets <- uassets.getConfirmedAndUnconfirmed(outIds).map(_.groupBy(_.boxId)).asStream
outsInfo = outs.map(out => AnyOutputInfo(out, assets.getOrElse(out.boxId, Nil)))
flattened <- Stream.emits(outsInfo.toList)
} yield flattened

private def toUnspentOutputInfo: Pipe[D, Chunk[Output], OutputInfo] =
for {
outs <- _
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.ergoplatform.explorer.db.models

import io.circe.Json
import org.ergoplatform.explorer._

final case class AnyOutput(
boxId: BoxId,
txId: TxId,
headerId: Option[BlockId],
value: Long,
creationHeight: Int,
settlementHeight: Option[Int],
index: Int,
globalIndex: Option[Long],
ergoTree: HexString,
ergoTreeTemplateHash: ErgoTreeTemplateHash,
address: Address,
additionalRegisters: Json,
timestamp: Option[Long],
mainChain: Option[Boolean],
spendingTxId: Option[TxId]
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ergoplatform.explorer.db.models.aggregates

import org.ergoplatform.explorer.{BlockId, BoxId, TokenId, TokenType}

final case class AnyAsset(
tokenId: TokenId,
boxId: BoxId,
headerId: Option[BlockId],
index: Int,
amount: Long,
name: Option[String],
decimals: Option[Int],
`type`: Option[TokenType]
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.data.NonEmptyList
import doobie.implicits._
import doobie.util.query.Query0
import doobie.{Fragments, LogHandler}
import org.ergoplatform.explorer.db.models.aggregates.{AggregatedAsset, ExtendedUAsset}
import org.ergoplatform.explorer.db.models.aggregates.{AggregatedAsset, AnyAsset, ExtendedUAsset}
import org.ergoplatform.explorer.{BoxId, HexString}

object UAssetQuerySet extends QuerySet {
Expand Down Expand Up @@ -49,6 +49,36 @@ object UAssetQuerySet extends QuerySet {
Fragments.in(fr"where a.box_id", boxIds))
.query[ExtendedUAsset]

def getConfirmedAndUnconfirmed(boxIds: NonEmptyList[BoxId])(implicit lh: LogHandler): Query0[AnyAsset] =
sql"""
|select distinct
| a.token_id,
| a.box_id,
| null,
| a.index,
| a.value,
| t.name,
| t.decimals,
| t.type
|from node_u_assets a
|left join tokens t on a.token_id = t.token_id
|${Fragments.in(fr"where a.box_id", boxIds)}
|union
|select distinct on (a.index, a.token_id, a.box_id)
| a.token_id,
| a.box_id,
| a.header_id,
| a.index,
| a.value,
| t.name,
| t.decimals,
| t.type
|from node_assets a
|left join tokens t on a.token_id = t.token_id
|${Fragments.in(fr"where a.box_id", boxIds)}
|""".stripMargin
.query[AnyAsset]

def getAllUnspentByErgoTree(ergoTree: HexString)(implicit lh: LogHandler): Query0[ExtendedUAsset] =
sql"""
|select
Expand Down
Loading

0 comments on commit d5ae595

Please sign in to comment.