Skip to content

Commit

Permalink
Merge pull request #2189 from ergoplatform/v5.0.25
Browse files Browse the repository at this point in the history
Candidate for 5.0.25 release
  • Loading branch information
kushti authored Feb 10, 2025
2 parents 8e14893 + 16bbdb8 commit a74d8f4
Show file tree
Hide file tree
Showing 18 changed files with 542 additions and 352 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ local.conf
.vscode/
project/metals.sbt
null/
out/

# scala worksheets
*.worksheet.sc
113 changes: 64 additions & 49 deletions ergo-core/src/main/scala/org/ergoplatform/settings/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,81 @@ import scala.concurrent.duration._

case class LoggingSettings(level: String)

case class RESTApiSettings(bindAddress: InetSocketAddress,
apiKeyHash: Option[String],
corsAllowedOrigin: Option[String],
timeout: FiniteDuration,
publicUrl: Option[URL])
case class RESTApiSettings(
bindAddress: InetSocketAddress,
apiKeyHash: Option[String],
corsAllowedOrigin: Option[String],
timeout: FiniteDuration,
publicUrl: Option[URL]
)

case class NetworkSettings(nodeName: String,
addedMaxDelay: Option[FiniteDuration],
localOnly: Boolean,
knownPeers: Seq[InetSocketAddress],
bindAddress: InetSocketAddress,
maxConnections: Int,
connectionTimeout: FiniteDuration,
upnpEnabled: Boolean,
upnpGatewayTimeout: Option[FiniteDuration],
upnpDiscoverTimeout: Option[FiniteDuration],
declaredAddress: Option[InetSocketAddress],
handshakeTimeout: FiniteDuration,
deliveryTimeout: FiniteDuration,
maxDeliveryChecks: Int,
appVersion: String,
agentName: String,
desiredInvObjects: Int,
syncInterval: FiniteDuration,
syncStatusRefresh: FiniteDuration,
syncIntervalStable: FiniteDuration,
syncStatusRefreshStable: FiniteDuration,
inactiveConnectionDeadline: FiniteDuration,
syncTimeout: Option[FiniteDuration],
controllerTimeout: Option[FiniteDuration],
maxModifiersCacheSize: Int,
magicBytes: Array[Byte],
getPeersInterval: FiniteDuration,
maxPeerSpecObjects: Int,
temporalBanDuration: FiniteDuration,
penaltySafeInterval: FiniteDuration,
penaltyScoreThreshold: Int,
peerEvictionInterval: FiniteDuration,
peerDiscovery: Boolean)

case class ScorexSettings(dataDir: File,
logDir: File,
logging: LoggingSettings,
network: NetworkSettings,
restApi: RESTApiSettings)
case class NetworkSettings(
nodeName: String,
addedMaxDelay: Option[FiniteDuration],
localOnly: Boolean,
knownPeers: Seq[InetSocketAddress],
bannedPeers: Seq[InetSocketAddress],
bindAddress: InetSocketAddress,
maxConnections: Int,
connectionTimeout: FiniteDuration,
upnpEnabled: Boolean,
upnpGatewayTimeout: Option[FiniteDuration],
upnpDiscoverTimeout: Option[FiniteDuration],
declaredAddress: Option[InetSocketAddress],
handshakeTimeout: FiniteDuration,
deliveryTimeout: FiniteDuration,
maxDeliveryChecks: Int,
appVersion: String,
agentName: String,
desiredInvObjects: Int,
syncInterval: FiniteDuration,
syncStatusRefresh: FiniteDuration,
syncIntervalStable: FiniteDuration,
syncStatusRefreshStable: FiniteDuration,
inactiveConnectionDeadline: FiniteDuration,
syncTimeout: Option[FiniteDuration],
controllerTimeout: Option[FiniteDuration],
maxModifiersCacheSize: Int,
magicBytes: Array[Byte],
getPeersInterval: FiniteDuration,
maxPeerSpecObjects: Int,
temporalBanDuration: FiniteDuration,
penaltySafeInterval: FiniteDuration,
penaltyScoreThreshold: Int,
peerEvictionInterval: FiniteDuration,
peerDiscovery: Boolean
)

case class ScorexSettings(
dataDir: File,
logDir: File,
logging: LoggingSettings,
network: NetworkSettings,
restApi: RESTApiSettings
)

object ScorexSettings extends ScorexLogging with SettingsReaders {

protected val configPath: String = "scorex"

def readConfigFromPath(userConfigPath: Option[String], configPath: String): Config = {

val maybeConfigFile: Option[File] = userConfigPath.map(filename => new File(filename)).filter(_.exists())
.orElse(userConfigPath.flatMap(filename => Option(getClass.getClassLoader.getResource(filename))).
map(r => new File(r.toURI)).filter(_.exists()))
val maybeConfigFile: Option[File] = userConfigPath
.map(filename => new File(filename))
.filter(_.exists())
.orElse(
userConfigPath
.flatMap(filename => Option(getClass.getClassLoader.getResource(filename)))
.map(r => new File(r.toURI))
.filter(_.exists())
)

val config = maybeConfigFile match {
// if no user config is supplied, the library will handle overrides/application/reference automatically
case None =>
log.warn("NO CONFIGURATION FILE WAS PROVIDED. STARTING WITH DEFAULT SETTINGS FOR TESTNET!")
log.warn(
"NO CONFIGURATION FILE WAS PROVIDED. STARTING WITH DEFAULT SETTINGS FOR TESTNET!"
)
ConfigFactory.load()
// application config needs to be resolved wrt both system properties *and* user-supplied config.
case Some(file) =>
Expand All @@ -96,7 +110,8 @@ object ScorexSettings extends ScorexLogging with SettingsReaders {
}

def fromConfig(config: Config): ScorexSettings = {
config.as[ScorexSettings](configPath)
config
.as[ScorexSettings](configPath)
.ensuring(_.network.magicBytes.length == MagicLength)
}
}
2 changes: 1 addition & 1 deletion src/main/resources/api/openapi-ai.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: "3.0.2"

info:
version: "5.0.24"
version: "5.0.25"
title: Ergo Node API
description: Specification of Ergo Node API for ChatGPT plugin.
The following endpoints supported
Expand Down
54 changes: 53 additions & 1 deletion src/main/resources/api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: "3.0.2"

info:
version: "5.0.24"
version: "5.0.25"
title: Ergo Node API
description: API docs for Ergo Node. Models are shared between all Ergo products
contact:
Expand Down Expand Up @@ -3261,6 +3261,58 @@ paths:
schema:
$ref: '#/components/schemas/ApiError'

/transactions/unconfirmed/transactionIds:
get:
summary: Get list of unconfirmed transactions ids
operationId: getUnconfirmedTxIds
tags:
- transactions
responses:
'200':
description: List of unconfirmed transaction ids
content:
application/json:
schema:
type: array
items:
type: string
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schema/ApiError'

/transactions/unconfirmed/byTransactionIds:
post:
summary: Get list of unconfirmed transactions by their ids
operationId: getUnconfirmedTxsByIds
tags:
- transactions
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
type: string
responses:
'200':
description: List of unconfirmed transaction ids
content:
application/json:
schema:
type: array
items:
type: string
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'

/transactions/unconfirmed/byErgoTree:
parameters:
- in: query
Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ scorex {
nodeName = "ergo-node"

# Network protocol version to be sent in handshakes
appVersion = 5.0.24
appVersion = 5.0.25

# Network agent name. May contain information about client code
# stack, starting from core code-base up to the end graphical interface.
Expand Down Expand Up @@ -498,6 +498,9 @@ scorex {
# A list of `IP:port` pairs of well known nodes.
knownPeers = []

# A list of `IP:port` pairs peers that will be permanently banned
bannedPeers = []

# Interval between GetPeers messages to be send by our node to a random one
getPeersInterval = 2m

Expand Down
11 changes: 9 additions & 2 deletions src/main/scala/org/ergoplatform/http/api/InfoApiRoute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import akka.http.scaladsl.model.ContentTypes
import akka.http.scaladsl.server.Route
import akka.pattern.ask
import io.circe.syntax._
import io.circe.Json
import org.ergoplatform.local.ErgoStatsCollector.{GetNodeInfo, NodeInfo}
import org.ergoplatform.settings.RESTApiSettings
import scorex.core.api.http.ApiResponse
Expand All @@ -19,8 +20,14 @@ case class InfoApiRoute(statsCollector: ActorRef,

override val route: Route = {
(path("info") & get) {
val timeJson = Map("currentTime" -> System.currentTimeMillis().asJson).asJson
ApiResponse((statsCollector ? GetNodeInfo).mapTo[NodeInfo].map(_.asJson.deepMerge(timeJson)))
val timeJson = Map(
"currentTime" -> System.currentTimeMillis().asJson
).asJson
ApiResponse((statsCollector ? GetNodeInfo).mapTo[NodeInfo].map { nodeInfo =>
nodeInfo.asJson.deepMerge(timeJson).deepMerge(Json.obj(
"lastMemPoolUpdateTime" -> nodeInfo.lastMemPoolUpdateTime.asJson
))
})
} ~
(path(".well-known" / "ai-plugin.json") & get) {
getFromResource(".well-known/ai-plugin.json", ContentTypes.`application/json`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ case class TransactionsApiRoute(readersHolder: ActorRef,
getUnconfirmedOutputByBoxIdR ~
getUnconfirmedInputByBoxIdR ~
getUnconfirmedTxsByErgoTreeR ~
getUnconfirmedTxIdsR ~
getUnconfirmedTxByIdR ~
getUnconfirmedTxsByIdsR ~
getUnconfirmedTransactionsR ~
unconfirmedContainsR ~
sendTransactionR ~
Expand Down Expand Up @@ -167,6 +169,29 @@ case class TransactionsApiRoute(readersHolder: ActorRef,
ApiResponse(getMemPool.map(_.modifierById(modifierId)))
}

/** Get list of unconfirmed transaction ids */
def getUnconfirmedTxIdsR: Route =
(pathPrefix("unconfirmed" / "transactionIds") & get) {
ApiResponse(getMemPool.map(_.getAll.map(_.id)))
}

/** Post list of unconfirmed transaction ids and check if they are in the mempool */
def getUnconfirmedTxsByIdsR: Route =
(pathPrefix("unconfirmed" / "byTransactionIds") & post & entity(as[Json])) { txIds =>
txIds.as[List[String]] match {
case Left(ex) =>
ApiError(StatusCodes.BadRequest, ex.getMessage())
case Right(ids) =>
ApiResponse(
getMemPool.map { pool =>
pool.getAll
.filter(tx => ids.contains(tx.id))
.map(_.id)
}
)
}
}

/** Collect all transactions which inputs or outputs contain given ErgoTree hex */
def getUnconfirmedTxsByErgoTreeR: Route =
(pathPrefix("unconfirmed" / "byErgoTree") & post & entity(as[Json]) & txPaging) { case (body, offset, limit) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class ErgoStatsCollector(readersHolder: ActorRef,
None,
launchTime = System.currentTimeMillis(),
lastIncomingMessageTime = System.currentTimeMillis(),
lastMemPoolUpdateTime = System.currentTimeMillis(),
None,
LaunchParameters,
eip27Supported = true,
Expand Down Expand Up @@ -100,6 +101,7 @@ class ErgoStatsCollector(readersHolder: ActorRef,

private def onMempoolChanged: Receive = {
case ChangedMempool(p) =>
nodeInfo = nodeInfo.copy(lastMemPoolUpdateTime = System.currentTimeMillis())
nodeInfo = nodeInfo.copy(unconfirmedCount = p.size)
}

Expand Down Expand Up @@ -171,6 +173,7 @@ object ErgoStatsCollector {
* @param maxPeerHeight - maximum block height of connected peers
* @param launchTime - when the node was launched (in Java time format, basically, UNIX time * 1000)
* @param lastIncomingMessageTime - when the node received last p2p message (in Java time)
* @param lastMemPoolUpdateTime - when the mempool was last updated (in Java time)
* @param genesisBlockIdOpt - header id of genesis block
* @param parameters - array with network parameters at the moment
* @param eip27Supported - whether EIP-27 locked in
Expand All @@ -193,6 +196,7 @@ object ErgoStatsCollector {
maxPeerHeight : Option[Int],
launchTime: Long,
lastIncomingMessageTime: Long,
lastMemPoolUpdateTime: Long,
genesisBlockIdOpt: Option[String],
parameters: Parameters,
eip27Supported: Boolean,
Expand Down Expand Up @@ -227,6 +231,7 @@ object ErgoStatsCollector {
"peersCount" -> ni.peersCount.asJson,
"launchTime" -> ni.launchTime.asJson,
"lastSeenMessageTime" -> ni.lastIncomingMessageTime.asJson,
"lastMemPoolUpdateTime" -> ni.lastMemPoolUpdateTime.asJson,
"genesisBlockId" -> ni.genesisBlockIdOpt.asJson,
"parameters" -> ni.parameters.asJson,
"eip27Supported" -> ni.eip27Supported.asJson,
Expand Down
Loading

0 comments on commit a74d8f4

Please sign in to comment.