PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/kotlin/net/postchain/rell/runtime/rt_context.kt

https://bitbucket.org/chromawallet/rellr
Kotlin | 314 lines | 268 code | 43 blank | 3 comment | 34 complexity | d0c425f085b3cf683c5072fe124f105c MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception
  1. /*
  2. * Copyright (C) 2020 ChromaWay AB. See LICENSE for license information.
  3. */
  4. package net.postchain.rell.runtime
  5. import com.google.common.collect.Sets
  6. import mu.KLogging
  7. import net.postchain.base.BlockchainRid
  8. import net.postchain.core.ByteArrayKey
  9. import net.postchain.gtv.Gtv
  10. import net.postchain.rell.CommonUtils
  11. import net.postchain.rell.model.*
  12. import net.postchain.rell.repl.ReplOutputChannel
  13. import net.postchain.rell.sql.*
  14. import net.postchain.rell.toImmMap
  15. class Rt_GlobalContext(
  16. val outPrinter: Rt_Printer,
  17. val logPrinter: Rt_Printer,
  18. val opCtx: Rt_OpContext?,
  19. val chainCtx: Rt_ChainContext,
  20. val logSqlErrors: Boolean = false,
  21. val sqlUpdatePortionSize: Int = 1000, // Experimental maximum is 2^15
  22. val typeCheck: Boolean = false
  23. ){
  24. private val rellVersion = Rt_RellVersion.getInstance()
  25. fun rellVersion(): Rt_RellVersion {
  26. return rellVersion ?: throw Rt_Error("fn:rell.git_info:no_data", "Version information not found")
  27. }
  28. }
  29. class Rt_SqlContext private constructor(
  30. app: R_App,
  31. val mainChainMapping: Rt_ChainSqlMapping,
  32. private val linkedExternalChains: List<Rt_ExternalChain>
  33. ) {
  34. val appDefs = app.sqlDefs
  35. private val externalChainsRoot = app.externalChainsRoot
  36. fun linkedChain(chain: R_ExternalChainRef): Rt_ExternalChain {
  37. check(chain.root === externalChainsRoot)
  38. return linkedExternalChains[chain.index]
  39. }
  40. fun chainMapping(externalChain: R_ExternalChainRef?): Rt_ChainSqlMapping {
  41. return if (externalChain == null) mainChainMapping else linkedChain(externalChain).sqlMapping
  42. }
  43. companion object : KLogging() {
  44. fun createNoExternalChains(app: R_App, mainChainMapping: Rt_ChainSqlMapping): Rt_SqlContext {
  45. require(app.valid)
  46. require(app.externalChains.isEmpty()) { "App uses external chains" }
  47. return Rt_SqlContext(app, mainChainMapping, listOf())
  48. }
  49. fun create(
  50. app: R_App,
  51. mainChainMapping: Rt_ChainSqlMapping,
  52. chainDependencies: Map<String, Rt_ChainDependency>,
  53. sqlExec: SqlExecutor,
  54. heightProvider: Rt_ChainHeightProvider
  55. ): Rt_SqlContext {
  56. require(app.valid)
  57. val externalChains = getExternalChains(sqlExec, chainDependencies, heightProvider)
  58. val linkedExternalChains = calcLinkedExternalChains(app, externalChains)
  59. val sqlCtx = Rt_SqlContext(app, mainChainMapping, linkedExternalChains)
  60. checkExternalMetaInfo(sqlCtx, externalChains, sqlExec)
  61. return sqlCtx
  62. }
  63. private fun getExternalChains(
  64. sqlExec: SqlExecutor,
  65. dependencies: Map<String, Rt_ChainDependency>,
  66. heightProvider: Rt_ChainHeightProvider
  67. ): Map<String, Rt_ExternalChain> {
  68. if (dependencies.isEmpty()) return mapOf()
  69. val rids = mutableSetOf<String>()
  70. for ((name, dep) in dependencies) {
  71. val ridStr = CommonUtils.bytesToHex(dep.rid)
  72. if (!rids.add(ridStr)) {
  73. throw errInit("external_chain_dup_rid:$name:$ridStr",
  74. "Duplicate external chain RID: '$name', 0x$ridStr")
  75. }
  76. }
  77. val dbChains = loadDatabaseBlockchains(sqlExec)
  78. val dbRidMap = dbChains.map { (chainId, rid) -> Pair(CommonUtils.bytesToHex(rid), chainId) }.toMap()
  79. val res = mutableMapOf<String, Rt_ExternalChain>()
  80. for ((name, dep) in dependencies) {
  81. val ridStr = CommonUtils.bytesToHex(dep.rid)
  82. val chainId = dbRidMap[ridStr]
  83. if (chainId == null) {
  84. throw errInit("external_chain_no_rid:$name:$ridStr",
  85. "External chain '$name' not found in the database by RID 0x$ridStr")
  86. }
  87. val ridKey = ByteArrayKey(dep.rid)
  88. val height = heightProvider.getChainHeight(ridKey, chainId)
  89. if (height == null) {
  90. throw errInit("external_chain_no_height:$name:$ridStr:$chainId",
  91. "Unknown height of the external chain '$name' (RID: 0x$ridStr, ID: $chainId)")
  92. }
  93. res[name] = Rt_ExternalChain(chainId, dep.rid, height)
  94. }
  95. return res
  96. }
  97. private fun loadDatabaseBlockchains(sqlExec: SqlExecutor): Map<Long, ByteArray> {
  98. val res = mutableMapOf<Long, ByteArray>()
  99. sqlExec.executeQuery("SELECT chain_iid, blockchain_rid FROM blockchains ORDER BY chain_iid;", {}) { rs ->
  100. val chainId = rs.getLong(1)
  101. val rid = rs.getBytes(2)
  102. check(chainId !in res)
  103. res[chainId] = rid
  104. }
  105. return res
  106. }
  107. private fun calcLinkedExternalChains(
  108. app: R_App,
  109. externalChains: Map<String, Rt_ExternalChain>
  110. ): List<Rt_ExternalChain> {
  111. val chainIds = mutableSetOf<Long>()
  112. val chainRids = mutableSetOf<String>()
  113. for ((name, c) in externalChains) {
  114. val id = c.chainId
  115. val rid = CommonUtils.bytesToHex(c.rid)
  116. if (!chainIds.add(id)) {
  117. throw errInit("external_chain_dup_id:$name:$id", "Duplicate external chain ID: '$name', $id")
  118. }
  119. if (!chainRids.add(rid)) {
  120. throw errInit("external_chain_dup_rid:$name:$rid", "Duplicate external chain RID: '$name', 0x$rid")
  121. }
  122. }
  123. return app.externalChains.map { rChain ->
  124. val name = rChain.name
  125. val rtChain = externalChains[name]
  126. if (rtChain == null) {
  127. throw errInit("external_chain_unknown:$name", "External chain not found: '$name'")
  128. }
  129. rtChain!!
  130. }
  131. }
  132. private fun checkExternalMetaInfo(sqlCtx: Rt_SqlContext, chains: Map<String, Rt_ExternalChain>, sqlExec: SqlExecutor) {
  133. val chainMetaEntities = chains.mapValues { (name, chain) -> loadExternalMetaData(name, chain, sqlExec) }
  134. val chainExternalEntities = getChainExternalEntities(sqlCtx.appDefs.entities)
  135. for (chain in chainExternalEntities.keys) {
  136. val extEntities = chainExternalEntities.getValue(chain)
  137. val metaEntities = chainMetaEntities.getOrDefault(chain, mapOf())
  138. checkMissingEntities(chain, extEntities, metaEntities)
  139. for (entityName in extEntities.keys.sorted()) {
  140. val extEntity = extEntities.getValue(entityName)
  141. val metaEntity = metaEntities.getValue(entityName)
  142. if (!metaEntity.log) {
  143. throw errInit("external_meta_nolog:$chain:$entityName",
  144. "Entity '$entityName' in external chain '$chain' is not a log entity")
  145. }
  146. checkMissingAttrs(chain, extEntity, metaEntity)
  147. checkAttrTypes(sqlCtx, chain, extEntity, metaEntity)
  148. }
  149. }
  150. }
  151. private fun checkMissingEntities(chain: String, extEntities: Map<String, R_Entity>, metaEntities: Map<String, MetaEntity>) {
  152. val metaEntityNames = metaEntities.filter { (_, c) -> c.type == MetaEntityType.ENTITY }.keys
  153. val missingEntities = Sets.difference(extEntities.keys, metaEntityNames)
  154. if (!missingEntities.isEmpty()) {
  155. val list = missingEntities.sorted()
  156. throw errInit("external_meta_no_entity:$chain:${list.joinToString(",")}",
  157. "Entities not found in external chain '$chain': ${list.joinToString()}")
  158. }
  159. }
  160. private fun checkMissingAttrs(chain: String, extEntity: R_Entity, metaEntity: MetaEntity) {
  161. val metaAttrNames = metaEntity.attrs.keys
  162. val extAttrNames = extEntity.attributes.keys
  163. val missingAttrs = Sets.difference(extAttrNames, metaAttrNames)
  164. if (!missingAttrs.isEmpty()) {
  165. val entityName = extEntity.appLevelName
  166. val list = missingAttrs.sorted()
  167. throw errInit("external_meta_noattrs:$chain:[$entityName]:${list.joinToString(",")}",
  168. "Missing attributes of entity '$entityName' in external chain '$chain': ${list.joinToString()}")
  169. }
  170. }
  171. private fun checkAttrTypes(sqlCtx: Rt_SqlContext, chain: String, extEntity: R_Entity, metaEntity: MetaEntity) {
  172. for (extAttr in extEntity.attributes.values.sortedBy { it.name }) {
  173. val attrName = extAttr.name
  174. val metaAttr = metaEntity.attrs.getValue(attrName)
  175. val metaType = metaAttr.type
  176. val extType = extAttr.type.sqlAdapter.metaName(sqlCtx)
  177. if (metaType != extType) {
  178. val entityName = extEntity.appLevelName
  179. throw errInit("external_meta_attrtype:$chain:[$entityName]:$attrName:[$extType]:[$metaType]",
  180. "Attribute type mismatch for '$entityName.$attrName' in external chain '$chain': " +
  181. "expected '$extType', actual '$metaType'")
  182. }
  183. }
  184. }
  185. private fun getChainExternalEntities(entities: List<R_Entity>): Map<String, Map<String, R_Entity>> {
  186. val res = mutableMapOf<String, MutableMap<String, R_Entity>>()
  187. for (entity in entities) {
  188. if (entity.external != null && entity.external.metaCheck) {
  189. val map = res.computeIfAbsent(entity.external.chain.name) { mutableMapOf() }
  190. check(entity.metaName !in map)
  191. map[entity.metaName] = entity
  192. }
  193. }
  194. return res
  195. }
  196. private fun loadExternalMetaData(name: String, chain: Rt_ExternalChain, sqlExec: SqlExecutor): Map<String, MetaEntity> {
  197. val res: Map<String, MetaEntity>
  198. val msgs = Rt_Messages(logger)
  199. try {
  200. res = SqlMeta.loadMetaData(sqlExec, chain.sqlMapping, msgs)
  201. msgs.checkErrors()
  202. } catch (e: Rt_Error) {
  203. throw errInit("external_meta_error:${chain.chainId}:$name:${e.code}",
  204. "Failed to load metadata for external chain '$name' (chain_iid = ${chain.chainId}): ${e.message}")
  205. }
  206. return res
  207. }
  208. private fun errInit(code: String, msg: String): RuntimeException = Rt_Error(code, msg)
  209. }
  210. }
  211. class Rt_AppContext(
  212. val globalCtx: Rt_GlobalContext,
  213. val sqlCtx: Rt_SqlContext,
  214. val app: R_App,
  215. val replOut: ReplOutputChannel?
  216. ) {
  217. private var objsInit: SqlObjectsInit? = null
  218. private var objsInited = false
  219. fun createRootFrame(defPos: R_DefinitionPos, sqlExec: SqlExecutor): Rt_CallFrame {
  220. val exeCtx = Rt_ExecutionContext(this, sqlExec)
  221. val defCtx = Rt_DefinitionContext(exeCtx, true, defPos)
  222. val containerUid = R_ContainerUid(0, "<init>", app.uid)
  223. val fnUid = R_FnUid(0, "<init>", containerUid)
  224. val blockUid = R_FrameBlockUid(0, "<init>", fnUid)
  225. val rFrameBlock = R_FrameBlock(null, blockUid, 0, 0)
  226. val rFrame = R_CallFrame(0, rFrameBlock)
  227. return rFrame.createRtFrame(defCtx, null, null)
  228. }
  229. fun objectsInitialization(objsInit: SqlObjectsInit, code: () -> Unit) {
  230. check(this.objsInit == null)
  231. check(!objsInited)
  232. objsInited = true
  233. this.objsInit = objsInit
  234. try {
  235. code()
  236. } finally {
  237. this.objsInit = null
  238. }
  239. }
  240. fun forceObjectInit(obj: R_Object): Boolean {
  241. val ref = objsInit
  242. return if (ref == null) false else {
  243. ref.forceObject(obj)
  244. true
  245. }
  246. }
  247. }
  248. class Rt_ExecutionContext(val appCtx: Rt_AppContext, val sqlExec: SqlExecutor) {
  249. val globalCtx = appCtx.globalCtx
  250. }
  251. class Rt_CallContext(val defCtx: Rt_DefinitionContext) {
  252. val exeCtx = defCtx.exeCtx
  253. val appCtx = exeCtx.appCtx
  254. val globalCtx = appCtx.globalCtx
  255. val chainCtx = globalCtx.chainCtx
  256. }
  257. class Rt_DefinitionContext(val exeCtx: Rt_ExecutionContext, val dbUpdateAllowed: Boolean, val pos: R_DefinitionPos) {
  258. val appCtx = exeCtx.appCtx
  259. val globalCtx = appCtx.globalCtx
  260. val sqlCtx = appCtx.sqlCtx
  261. val callCtx = Rt_CallContext(this)
  262. fun checkDbUpdateAllowed() {
  263. if (!dbUpdateAllowed) {
  264. throw Rt_Error("no_db_update", "Database modifications are not allowed in this context")
  265. }
  266. }
  267. }
  268. class Rt_OpContext(val lastBlockTime: Long, val transactionIid: Long, val blockHeight: Long, val signers: List<ByteArray>)
  269. class Rt_ChainContext(val rawConfig: Gtv, args: Map<R_ModuleName, Rt_Value>, val blockchainRid: BlockchainRid) {
  270. val args = args.toImmMap()
  271. }