diff --git a/app/src/main/resources/explorer-backend-openapi.json b/app/src/main/resources/explorer-backend-openapi.json index 878cc2a56..9fb90d3bc 100644 --- a/app/src/main/resources/explorer-backend-openapi.json +++ b/app/src/main/resources/explorer-backend-openapi.json @@ -61,6 +61,9 @@ "$ref": "#/components/schemas/Token" } }, + "fixedOutput": { + "type": "boolean" + }, "type": { "type": "string" }, @@ -74,6 +77,7 @@ "key", "attoAlphAmount", "address", + "fixedOutput", "type" ] }, @@ -188,6 +192,9 @@ "format": "address", "type": "string" }, + "contractInput": { + "type": "boolean" + }, "unlockScript": { "format": "hex-string", "type": "string" @@ -211,7 +218,8 @@ } }, "required": [ - "outputRef" + "outputRef", + "contractInput" ] }, "PerChainCount": { @@ -253,6 +261,94 @@ "type" ] }, + "BlockEntry": { + "type": "object", + "title": "BlockEntry", + "properties": { + "parent": { + "format": "block-hash", + "type": "string" + }, + "chainFrom": { + "format": "group-index", + "type": "integer" + }, + "depStateHash": { + "format": "32-byte-hash", + "type": "string" + }, + "deps": { + "type": "array", + "items": { + "format": "block-hash", + "type": "string" + } + }, + "nonce": { + "format": "hex-string", + "type": "string" + }, + "version": { + "type": "integer" + }, + "target": { + "format": "hex-string", + "type": "string" + }, + "mainChain": { + "type": "boolean" + }, + "txsHash": { + "format": "32-byte-hash", + "type": "string" + }, + "chainTo": { + "format": "group-index", + "type": "integer" + }, + "ghostUncles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GhostUncle" + } + }, + "hashRate": { + "format": "bigint", + "type": "string" + }, + "txNumber": { + "format": "int32", + "type": "integer" + }, + "hash": { + "format": "block-hash", + "type": "string" + }, + "height": { + "format": "int32", + "type": "integer" + }, + "timestamp": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "hash", + "timestamp", + "chainFrom", + "chainTo", + "height", + "nonce", + "version", + "depStateHash", + "txsHash", + "txNumber", + "target", + "hashRate", + "mainChain" + ] + }, "ContractParent": { "type": "object", "title": "ContractParent", @@ -344,6 +440,9 @@ "$ref": "#/components/schemas/Token" } }, + "fixedOutput": { + "type": "boolean" + }, "type": { "type": "string" }, @@ -361,6 +460,7 @@ "key", "attoAlphAmount", "address", + "fixedOutput", "type" ] }, @@ -390,8 +490,12 @@ "format": "block-hash", "type": "string" }, - "coinbase": { - "type": "boolean" + "scriptSignatures": { + "type": "array", + "items": { + "format": "hex-string", + "type": "string" + } }, "inputs": { "type": "array", @@ -399,16 +503,35 @@ "$ref": "#/components/schemas/Input" } }, - "gasAmount": { - "format": "int32", - "type": "integer" - }, "scriptExecutionOk": { "type": "boolean" }, "type": { "type": "string" }, + "scriptOpt": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "coinbase": { + "type": "boolean" + }, + "inputSignatures": { + "type": "array", + "items": { + "format": "hex-string", + "type": "string" + } + }, + "gasAmount": { + "format": "int32", + "type": "integer" + }, + "networkId": { + "type": "integer" + }, "hash": { "format": "32-byte-hash", "type": "string" @@ -426,6 +549,8 @@ "hash", "blockHash", "timestamp", + "version", + "networkId", "gasAmount", "gasPrice", "scriptExecutionOk", @@ -769,8 +894,12 @@ "format": "block-hash", "type": "string" }, - "coinbase": { - "type": "boolean" + "scriptSignatures": { + "type": "array", + "items": { + "format": "hex-string", + "type": "string" + } }, "inputs": { "type": "array", @@ -778,12 +907,31 @@ "$ref": "#/components/schemas/Input" } }, + "scriptExecutionOk": { + "type": "boolean" + }, + "scriptOpt": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "coinbase": { + "type": "boolean" + }, + "inputSignatures": { + "type": "array", + "items": { + "format": "hex-string", + "type": "string" + } + }, "gasAmount": { "format": "int32", "type": "integer" }, - "scriptExecutionOk": { - "type": "boolean" + "networkId": { + "type": "integer" }, "hash": { "format": "32-byte-hash", @@ -802,6 +950,8 @@ "hash", "blockHash", "timestamp", + "version", + "networkId", "gasAmount", "gasPrice", "scriptExecutionOk", @@ -820,6 +970,24 @@ "type" ] }, + "GhostUncle": { + "type": "object", + "title": "GhostUncle", + "properties": { + "blockHash": { + "format": "block-hash", + "type": "string" + }, + "miner": { + "format": "address", + "type": "string" + } + }, + "required": [ + "blockHash", + "miner" + ] + }, "NFTCollectionWithRoyalty": { "type": "object", "title": "NFTCollectionWithRoyalty", @@ -2964,6 +3132,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -2982,6 +3151,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } @@ -2992,6 +3162,7 @@ "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3032,6 +3203,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3050,6 +3222,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } @@ -3060,6 +3233,7 @@ "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3202,6 +3376,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3220,15 +3395,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3248,8 +3427,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -3271,6 +3456,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3289,15 +3475,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3317,8 +3507,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -3474,6 +3670,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3492,15 +3689,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3520,8 +3721,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -3543,6 +3750,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3561,15 +3769,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3589,8 +3801,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -3832,6 +4050,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -3850,15 +4069,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -3878,9 +4101,15 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, "type": "Accepted", + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4003,6 +4232,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4021,15 +4251,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4049,8 +4283,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4072,6 +4312,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4090,15 +4331,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4118,8 +4363,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4286,6 +4537,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4304,15 +4556,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4332,8 +4588,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4355,6 +4617,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4373,15 +4636,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4401,8 +4668,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4700,6 +4973,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4718,15 +4992,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4746,8 +5024,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -4769,6 +5053,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -4787,15 +5072,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -4815,8 +5104,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -5230,12 +5525,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BlockEntryLite" + "$ref": "#/components/schemas/BlockEntry" }, "example": { - "mainChain": true, + "parent": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", "chainFrom": 1, + "depStateHash": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", + "deps": [ + "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5" + ], + "nonce": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", + "version": 1, + "target": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", + "mainChain": true, + "txsHash": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "chainTo": 2, + "ghostUncles": [ + { + "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", + "miner": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y" + } + ], "hashRate": "147573952589676412928", "txNumber": 1, "hash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", @@ -5590,6 +5900,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -5608,15 +5919,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -5636,8 +5951,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -5659,6 +5980,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -5677,15 +5999,19 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } ], "blockHash": "bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5", - "coinbase": false, + "scriptSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -5705,8 +6031,14 @@ } } ], - "gasAmount": 20000, "scriptExecutionOk": true, + "version": 1, + "coinbase": false, + "inputSignatures": [ + "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" + ], + "gasAmount": 20000, + "networkId": 0, "hash": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69", "gasPrice": "100000000000", "timestamp": 1611041396892 @@ -8243,6 +8575,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -8261,6 +8594,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } @@ -8271,6 +8605,7 @@ "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ @@ -8311,6 +8646,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": true, "type": "AssetOutput", "message": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" @@ -8329,6 +8665,7 @@ "id": "bd165d20bd063c7a023d22232a1e75bf46e904067f92b49323fe89fa0fd586bf" } ], + "fixedOutput": false, "type": "ContractOutput", "key": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef" } @@ -8339,6 +8676,7 @@ "inputs": [ { "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y", + "contractInput": false, "unlockScript": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28", "attoAlphAmount": "2", "tokens": [ diff --git a/app/src/main/scala/org/alephium/explorer/api/BlockEndpoints.scala b/app/src/main/scala/org/alephium/explorer/api/BlockEndpoints.scala index c19512cd5..0f0b75c5b 100644 --- a/app/src/main/scala/org/alephium/explorer/api/BlockEndpoints.scala +++ b/app/src/main/scala/org/alephium/explorer/api/BlockEndpoints.scala @@ -33,10 +33,10 @@ trait BlockEndpoints extends BaseEndpoint with QueryParams { .tag("Blocks") .in("blocks") - val getBlockByHash: BaseEndpoint[BlockHash, BlockEntryLite] = + val getBlockByHash: BaseEndpoint[BlockHash, BlockEntry] = blocksEndpoint.get .in(path[BlockHash]("block_hash")) - .out(jsonBody[BlockEntryLite]) + .out(jsonBody[BlockEntry]) .description("Get a block with hash") val getBlockTransactions: BaseEndpoint[(BlockHash, Pagination), ArraySeq[Transaction]] = diff --git a/app/src/main/scala/org/alephium/explorer/api/EndpointExamples.scala b/app/src/main/scala/org/alephium/explorer/api/EndpointExamples.scala index e532f1cb0..f722b45ea 100644 --- a/app/src/main/scala/org/alephium/explorer/api/EndpointExamples.scala +++ b/app/src/main/scala/org/alephium/explorer/api/EndpointExamples.scala @@ -44,6 +44,9 @@ object EndpointExamples extends EndpointsExamples { .from(Hex.unsafe("bdaf9dc514ce7d34b6474b8ca10a3dfb93ba997cb9d5ff1ea724ebe2af48abe5")) .get + val version: Byte = 1 + val networkId: Byte = 0 + private val outputRef: OutputRef = OutputRef(hint = 23412, key = hash) @@ -66,6 +69,8 @@ object EndpointExamples extends EndpointsExamples { contract ) + private val addressAsset: Address.Asset = Address.asset(address1.toBase58).get + private val groupIndex1: GroupIndex = new GroupIndex(1) private val groupIndex2: GroupIndex = new GroupIndex(2) @@ -84,7 +89,8 @@ object EndpointExamples extends EndpointsExamples { txHashRef = Some(txId), address = Some(address1), attoAlphAmount = Some(U256.Two), - tokens = Some(tokens) + tokens = Some(tokens), + contractInput = false ) private val outputAsset: AssetOutput = @@ -95,7 +101,8 @@ object EndpointExamples extends EndpointsExamples { address = address1, tokens = Some(tokens), lockTime = Some(ts), - message = Some(hash.bytes) + message = Some(hash.bytes), + fixedOutput = true ) private val outputContract: Output = @@ -104,7 +111,8 @@ object EndpointExamples extends EndpointsExamples { key = hash, attoAlphAmount = U256.Two, address = address1, - tokens = Some(tokens) + tokens = Some(tokens), + fixedOutput = false ) /** Main API objects @@ -121,6 +129,26 @@ object EndpointExamples extends EndpointsExamples { hashRate = HashRate.a128EhPerSecond.value ) + private val blockEntry: BlockEntry = + BlockEntry( + hash = blockHash, + timestamp = ts, + chainFrom = groupIndex1, + chainTo = groupIndex2, + height = Height.unsafe(42), + deps = ArraySeq(blockHash), + nonce = hash.bytes, + version = 1, + depStateHash = hash, + txsHash = hash, + txNumber = 1, + target = hash.bytes, + hashRate = HashRate.a128EhPerSecond.value, + parent = Some(blockHash), + mainChain = true, + ghostUncles = ArraySeq(GhostUncle(blockHash, addressAsset)) + ) + private val transaction: Transaction = Transaction( hash = txId, @@ -128,9 +156,14 @@ object EndpointExamples extends EndpointsExamples { timestamp = ts, inputs = ArraySeq(input), outputs = ArraySeq(outputAsset, outputContract), + version = version, + networkId = networkId, + scriptOpt = None, gasAmount = org.alephium.protocol.model.minimalGas.value, gasPrice = org.alephium.protocol.model.nonCoinbaseMinGasPrice.value, scriptExecutionOk = true, + inputSignatures = ArraySeq(hash.bytes), + scriptSignatures = ArraySeq(hash.bytes), coinbase = false ) @@ -141,9 +174,14 @@ object EndpointExamples extends EndpointsExamples { timestamp = ts, inputs = ArraySeq(input), outputs = ArraySeq(outputAsset, outputContract), + version = version, + networkId = networkId, + scriptOpt = None, gasAmount = org.alephium.protocol.model.minimalGas.value, gasPrice = org.alephium.protocol.model.nonCoinbaseMinGasPrice.value, scriptExecutionOk = true, + inputSignatures = ArraySeq(hash.bytes), + scriptSignatures = ArraySeq(hash.bytes), coinbase = false ) @@ -313,6 +351,9 @@ object EndpointExamples extends EndpointsExamples { implicit val blockEntryLiteExample: List[Example[BlockEntryLite]] = simpleExample(blockEntryLite) + implicit val blockEntryExample: List[Example[BlockEntry]] = + simpleExample(blockEntry) + implicit val transactionsExample: List[Example[ArraySeq[Transaction]]] = simpleExample(ArraySeq(transaction, transaction)) diff --git a/app/src/main/scala/org/alephium/explorer/api/model/BlockEntry.scala b/app/src/main/scala/org/alephium/explorer/api/model/BlockEntry.scala index 48b85d88d..a427b546e 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/BlockEntry.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/BlockEntry.scala @@ -20,13 +20,16 @@ import java.math.BigInteger import scala.collection.immutable.ArraySeq +import akka.util.ByteString + import org.alephium.api.UtilJson._ import org.alephium.explorer.api.Codecs._ import org.alephium.explorer.api.Json.groupIndexReadWriter import org.alephium.explorer.service.FlowEntity import org.alephium.json.Json._ +import org.alephium.protocol.Hash import org.alephium.protocol.model.{BlockHash, GroupIndex} -import org.alephium.util.TimeStamp +import org.alephium.util.{AVector, TimeStamp} final case class BlockEntry( hash: BlockHash, @@ -35,10 +38,39 @@ final case class BlockEntry( chainTo: GroupIndex, height: Height, deps: ArraySeq[BlockHash], - transactions: ArraySeq[Transaction], + nonce: ByteString, + version: Byte, + depStateHash: Hash, + txsHash: Hash, + txNumber: Int, + target: ByteString, + hashRate: BigInteger, + parent: Option[BlockHash], mainChain: Boolean, - hashRate: BigInteger -) extends FlowEntity + ghostUncles: ArraySeq[GhostUncle] +) extends FlowEntity { + + def toProtocol( + transactions: ArraySeq[Transaction] + ): org.alephium.api.model.BlockEntry = { + org.alephium.api.model.BlockEntry( + hash, + timestamp, + chainFrom.value, + chainTo.value, + height.value, + AVector.from(deps), + AVector.from(transactions.map(_.toProtocol())), + nonce, + version, + depStateHash, + txsHash, + target, + AVector.from(ghostUncles.map(_.toProtocol())) + ) + } + +} object BlockEntry { implicit val codec: ReadWriter[BlockEntry] = macroRW diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockDepEntity.scala b/app/src/main/scala/org/alephium/explorer/api/model/GhostUncle.scala similarity index 62% rename from app/src/main/scala/org/alephium/explorer/persistence/model/BlockDepEntity.scala rename to app/src/main/scala/org/alephium/explorer/api/model/GhostUncle.scala index 2a1130e57..af93bdee6 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockDepEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/GhostUncle.scala @@ -14,10 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the library. If not, see . -package org.alephium.explorer.persistence.model +package org.alephium.explorer.api.model -import org.alephium.protocol.model.BlockHash +import org.alephium.api.model.GhostUncleBlockEntry +import org.alephium.explorer.api.Codecs._ +import org.alephium.json.Json._ +import org.alephium.protocol.model.{Address, BlockHash} -/** Class for defining rows in table [[org.alephium.explorer.persistence.schema.BlockDepsSchema]] - */ -final case class BlockDepEntity(hash: BlockHash, dep: BlockHash, order: Int) +final case class GhostUncle(blockHash: BlockHash, miner: Address.Asset) { + def toProtocol(): GhostUncleBlockEntry = GhostUncleBlockEntry(blockHash, miner) +} + +object GhostUncle { + implicit val codec: ReadWriter[GhostUncle] = macroRW +} diff --git a/app/src/main/scala/org/alephium/explorer/api/model/Input.scala b/app/src/main/scala/org/alephium/explorer/api/model/Input.scala index a65a43e68..279017cd9 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/Input.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/Input.scala @@ -21,6 +21,7 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString import sttp.tapir.Schema +import org.alephium.api.{model => protocol} import org.alephium.api.TapirSchemas._ import org.alephium.api.UtilJson._ import org.alephium.explorer.api.Json._ @@ -35,8 +36,15 @@ final case class Input( txHashRef: Option[TransactionId] = None, address: Option[Address] = None, attoAlphAmount: Option[U256] = None, - tokens: Option[ArraySeq[Token]] = None -) + tokens: Option[ArraySeq[Token]] = None, + contractInput: Boolean +) { + def toProtocol(): protocol.AssetInput = + protocol.AssetInput( + outputRef = outputRef.toProtocol(), + unlockScript = unlockScript.getOrElse(ByteString.empty) + ) +} object Input { implicit val readWriter: ReadWriter[Input] = macroRW diff --git a/app/src/main/scala/org/alephium/explorer/api/model/Output.scala b/app/src/main/scala/org/alephium/explorer/api/model/Output.scala index b46cd0e6e..3cec0dba3 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/Output.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/Output.scala @@ -29,6 +29,7 @@ import org.alephium.json.Json._ import org.alephium.protocol.Hash import org.alephium.protocol.model.{Address, TransactionId} import org.alephium.util.{TimeStamp, U256} +import org.alephium.util.AVector sealed trait Output { def hint: Int @@ -37,6 +38,7 @@ sealed trait Output { def address: Address def tokens: Option[ArraySeq[Token]] def spent: Option[TransactionId] + def fixedOutput: Boolean } @SuppressWarnings(Array("org.wartremover.warts.DefaultArguments")) @@ -49,8 +51,9 @@ final case class AssetOutput( tokens: Option[ArraySeq[Token]] = None, lockTime: Option[TimeStamp] = None, message: Option[ByteString] = None, - spent: Option[TransactionId] = None -) extends Output + spent: Option[TransactionId] = None, + fixedOutput: Boolean +) extends Output {} @SuppressWarnings(Array("org.wartremover.warts.DefaultArguments")) @upickle.implicits.key("ContractOutput") @@ -60,11 +63,69 @@ final case class ContractOutput( attoAlphAmount: U256, address: Address, tokens: Option[ArraySeq[Token]] = None, - spent: Option[TransactionId] = None + spent: Option[TransactionId] = None, + fixedOutput: Boolean ) extends Output object Output { + def toFixedAssetOutput( + output: Output + ): Option[org.alephium.api.model.FixedAssetOutput] = { + output match { + case asset: AssetOutput if asset.fixedOutput => + asset.address match { + case assetAddress: Address.Asset => + val amount = org.alephium.api.model.Amount(asset.attoAlphAmount) + Some( + org.alephium.api.model.FixedAssetOutput( + asset.hint, + asset.key, + amount, + assetAddress, + tokens = asset.tokens + .map(tokens => AVector.from(tokens.map(_.toProtocol()))) + .getOrElse(AVector.empty), + lockTime = asset.lockTime.getOrElse(TimeStamp.zero), + asset.message.getOrElse(ByteString.empty) + ) + ) + case _ => None + } + case _ => None + } + } + + def toProtocol(output: Output): Option[org.alephium.api.model.Output] = + (output, output.address) match { + case (asset: AssetOutput, assetAddress: Address.Asset) => + Some( + org.alephium.api.model.AssetOutput( + output.hint, + output.key, + org.alephium.api.model.Amount(output.attoAlphAmount), + assetAddress, + tokens = + output.tokens.map(t => AVector.from(t.map(_.toProtocol()))).getOrElse(AVector.empty), + lockTime = asset.lockTime.getOrElse(TimeStamp.zero), + message = asset.message.getOrElse(ByteString.empty) + ) + ) + case (_: ContractOutput, contractAddress: Address.Contract) => + Some( + org.alephium.api.model.ContractOutput( + output.hint, + output.key, + org.alephium.api.model.Amount(output.attoAlphAmount), + contractAddress, + tokens = output.tokens + .map(tokens => AVector.from(tokens.map(_.toProtocol()))) + .getOrElse(AVector.empty) + ) + ) + case _ => None + } + implicit val assetReadWriter: ReadWriter[AssetOutput] = macroRW implicit val contractReadWriter: ReadWriter[ContractOutput] = macroRW diff --git a/app/src/main/scala/org/alephium/explorer/api/model/OutputRef.scala b/app/src/main/scala/org/alephium/explorer/api/model/OutputRef.scala index 4fcc13e7c..86b2c0a6c 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/OutputRef.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/OutputRef.scala @@ -23,7 +23,11 @@ import org.alephium.explorer.api.Json._ import org.alephium.json.Json._ import org.alephium.protocol.Hash -final case class OutputRef(hint: Int, key: Hash) +final case class OutputRef(hint: Int, key: Hash) { + + def toProtocol(): org.alephium.api.model.OutputRef = + org.alephium.api.model.OutputRef(hint, key) +} object OutputRef { implicit val readWriter: ReadWriter[OutputRef] = macroRW diff --git a/app/src/main/scala/org/alephium/explorer/api/model/Token.scala b/app/src/main/scala/org/alephium/explorer/api/model/Token.scala index e5c0181b8..9e9e3ab43 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/Token.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/Token.scala @@ -26,7 +26,9 @@ import org.alephium.protocol.model.TokenId import org.alephium.serde._ import org.alephium.util.U256 -final case class Token(id: TokenId, amount: U256) +final case class Token(id: TokenId, amount: U256) { + def toProtocol(): org.alephium.api.model.Token = org.alephium.api.model.Token(id, amount) +} object Token { implicit val readWriter: ReadWriter[Token] = macroRW diff --git a/app/src/main/scala/org/alephium/explorer/api/model/Transaction.scala b/app/src/main/scala/org/alephium/explorer/api/model/Transaction.scala index 7ba3c3a04..1666a7077 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/Transaction.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/Transaction.scala @@ -20,10 +20,12 @@ import java.time.Instant import scala.collection.immutable.ArraySeq +import akka.util.ByteString import sttp.tapir.Schema +import org.alephium.api.{model => protocol} import org.alephium.api.TapirSchemas._ -import org.alephium.api.UtilJson.{timestampReader, timestampWriter} +import org.alephium.api.UtilJson._ import org.alephium.explorer.api.Json._ import org.alephium.explorer.util.UtxoUtil import org.alephium.json.Json._ @@ -31,6 +33,7 @@ import org.alephium.protocol.ALPH import org.alephium.protocol.model.{BlockHash, TransactionId} import org.alephium.protocol.model.Address import org.alephium.util.{TimeStamp, U256} +import org.alephium.util.AVector final case class Transaction( hash: TransactionId, @@ -38,9 +41,14 @@ final case class Transaction( timestamp: TimeStamp, inputs: ArraySeq[Input], outputs: ArraySeq[Output], + version: Byte, + networkId: Byte, + scriptOpt: Option[String], gasAmount: Int, gasPrice: U256, scriptExecutionOk: Boolean, + inputSignatures: ArraySeq[ByteString], + scriptSignatures: ArraySeq[ByteString], coinbase: Boolean ) { def toCsv(address: Address): String = { @@ -59,6 +67,29 @@ final case class Transaction( .getOrElse("") s"${hash.toHexString},${blockHash.toHexString},${timestamp.millis},$dateTime,$fromAddressesStr,$toAddresses,$amount,$amountHint\n" } + + def toProtocol(): org.alephium.api.model.Transaction = { + val (inputContracts, inputAssets) = inputs.partition(_.contractInput) + val (fixedOutputs, generatedOutputs) = outputs.partition(_.fixedOutput) + val unsigned: org.alephium.api.model.UnsignedTx = org.alephium.api.model.UnsignedTx( + txId = hash, + version = version, + networkId = networkId, + scriptOpt = scriptOpt.map(org.alephium.api.model.Script.apply), + gasAmount = gasAmount, + gasPrice = gasPrice, + inputs = AVector.from(inputAssets.map(_.toProtocol())), + fixedOutputs = AVector.from(fixedOutputs.flatMap(Output.toFixedAssetOutput)) + ) + org.alephium.api.model.Transaction( + unsigned = unsigned, + scriptExecutionOk = scriptExecutionOk, + contractInputs = AVector.from(inputContracts.map(_.outputRef.toProtocol())), + generatedOutputs = AVector.from(generatedOutputs.flatMap(Output.toProtocol)), + inputSignatures = AVector.from(inputSignatures), + scriptSignatures = AVector.from(scriptSignatures) + ) + } } object Transaction { diff --git a/app/src/main/scala/org/alephium/explorer/api/model/TransactionLike.scala b/app/src/main/scala/org/alephium/explorer/api/model/TransactionLike.scala index a79809288..dc068153b 100644 --- a/app/src/main/scala/org/alephium/explorer/api/model/TransactionLike.scala +++ b/app/src/main/scala/org/alephium/explorer/api/model/TransactionLike.scala @@ -18,7 +18,9 @@ package org.alephium.explorer.api.model import scala.collection.immutable.ArraySeq -import org.alephium.api.UtilJson.{timestampReader, timestampWriter} +import akka.util.ByteString + +import org.alephium.api.UtilJson._ import org.alephium.explorer.api.Json._ import org.alephium.json.Json._ import org.alephium.protocol.model.{BlockHash, GroupIndex, TransactionId} @@ -42,9 +44,14 @@ final case class AcceptedTransaction( timestamp: TimeStamp, inputs: ArraySeq[Input], outputs: ArraySeq[Output], + version: Byte, + networkId: Byte, + scriptOpt: Option[String], gasAmount: Int, gasPrice: U256, scriptExecutionOk: Boolean, + inputSignatures: ArraySeq[ByteString], + scriptSignatures: ArraySeq[ByteString], coinbase: Boolean ) extends TransactionLike @@ -56,9 +63,14 @@ object AcceptedTransaction { tx.timestamp, tx.inputs, tx.outputs, + tx.version, + tx.networkId, + tx.scriptOpt, tx.gasAmount, tx.gasPrice, tx.scriptExecutionOk, + tx.inputSignatures, + tx.scriptSignatures, tx.coinbase ) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/DBInitializer.scala b/app/src/main/scala/org/alephium/explorer/persistence/DBInitializer.scala index 571dd8c17..c0fb47f9a 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/DBInitializer.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/DBInitializer.scala @@ -43,7 +43,6 @@ object DBInitializer extends StrictLogging { val allTables = ArraySeq( BlockHeaderSchema.table, - BlockDepsSchema.table, TransactionSchema.table, InputSchema.table, OutputSchema.table, diff --git a/app/src/main/scala/org/alephium/explorer/persistence/dao/BlockDao.scala b/app/src/main/scala/org/alephium/explorer/persistence/dao/BlockDao.scala index 90760f468..12d36b9ba 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/dao/BlockDao.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/dao/BlockDao.scala @@ -44,7 +44,6 @@ import org.alephium.util.{Duration, TimeStamp} object BlockDao { def getLite(hash: BlockHash)(implicit - ec: ExecutionContext, dc: DatabaseConfig[PostgresProfile] ): Future[Option[BlockEntryLite]] = run(getBlockEntryLiteAction(hash)) diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/BlockEntity.scala index 7b9cbd95a..a3046f34d 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/BlockEntity.scala @@ -22,7 +22,7 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString -import org.alephium.explorer.api.model.Height +import org.alephium.explorer.api.model.{GhostUncle, Height} import org.alephium.explorer.service.FlowEntity import org.alephium.protocol.Hash import org.alephium.protocol.model.{BlockHash, GroupIndex} @@ -44,22 +44,9 @@ final case class BlockEntity( depStateHash: Hash, txsHash: Hash, target: ByteString, - hashrate: BigInteger + hashrate: BigInteger, + ghostUncles: ArraySeq[GhostUncle] ) extends FlowEntity { - def updateMainChain(newMainChain: Boolean): BlockEntity = { - this.copy( - mainChain = newMainChain, - transactions = transactions.map(_.copy(mainChain = newMainChain)), - inputs = inputs.map(_.copy(mainChain = newMainChain)), - outputs = outputs.map(_.copy(mainChain = newMainChain)) - ) - } - - /** Builds entries for block_deps table */ - def toBlockDepEntities(): ArraySeq[BlockDepEntity] = - deps.zipWithIndex map { case (dep, i) => - BlockDepEntity(hash = hash, dep = dep, order = i) - } @inline def toBlockHeader(groupNum: Int): BlockHeader = BlockHeader.fromEntity(this, groupNum) diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockHeader.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/BlockHeader.scala index 48966683c..2aa8842b4 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/BlockHeader.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/BlockHeader.scala @@ -22,7 +22,7 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString -import org.alephium.explorer.api.model.{BlockEntry, BlockEntryLite, Height, Transaction} +import org.alephium.explorer.api.model.{BlockEntry, BlockEntryLite, GhostUncle, Height} import org.alephium.protocol.Hash import org.alephium.protocol.model.{BlockHash, GroupIndex} import org.alephium.util.TimeStamp @@ -41,17 +41,47 @@ final case class BlockHeader( txsCount: Int, target: ByteString, hashrate: BigInteger, - parent: Option[BlockHash] + parent: Option[BlockHash], + deps: ArraySeq[BlockHash], + ghostUncles: Option[ArraySeq[GhostUncle]] ) { - def toApi(deps: ArraySeq[BlockHash], transactions: ArraySeq[Transaction]): BlockEntry = - BlockEntry(hash, timestamp, chainFrom, chainTo, height, deps, transactions, mainChain, hashrate) + + def toApi(): BlockEntry = + BlockEntry( + hash, + timestamp, + chainFrom, + chainTo, + height, + deps, + nonce, + version, + depStateHash, + txsHash, + txsCount, + target, + hashrate, + parent, + mainChain, + ghostUncles.getOrElse(ArraySeq.empty) + ) val toLiteApi: BlockEntryLite = - BlockEntryLite(hash, timestamp, chainFrom, chainTo, height, txsCount, mainChain, hashrate) + BlockEntryLite( + hash, + timestamp, + chainFrom, + chainTo, + height, + txsCount, + mainChain, + hashrate + ) } object BlockHeader { - def fromEntity(blockEntity: BlockEntity, groupNum: Int): BlockHeader = + def fromEntity(blockEntity: BlockEntity, groupNum: Int): BlockHeader = { + val ghostUncles = if (blockEntity.ghostUncles.isEmpty) None else Some(blockEntity.ghostUncles) BlockHeader( blockEntity.hash, blockEntity.timestamp, @@ -66,6 +96,9 @@ object BlockHeader { blockEntity.transactions.size, blockEntity.target, blockEntity.hashrate, - blockEntity.parent(groupNum) + blockEntity.parent(groupNum), + blockEntity.deps, + ghostUncles ) + } } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/InputEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/InputEntity.scala index 3005d0fd0..0aed40ce9 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/InputEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/InputEntity.scala @@ -20,11 +20,33 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString -import org.alephium.explorer.api.model.Token +import org.alephium.explorer.api.model.{Input, OutputRef, Token} import org.alephium.protocol.Hash import org.alephium.protocol.model.{Address, BlockHash, TransactionId} import org.alephium.util.{TimeStamp, U256} +trait InputEntityLike { + def hint: Int + def outputRefKey: Hash + def unlockScript: Option[ByteString] + def outputRefTxHash: Option[TransactionId] + def outputRefAddress: Option[Address] + def outputRefAmount: Option[U256] + def outputRefTokens: Option[ArraySeq[Token]] + def contractInput: Boolean + + def toApi(): Input = + Input( + outputRef = OutputRef(hint, outputRefKey), + unlockScript = unlockScript, + txHashRef = outputRefTxHash, + address = outputRefAddress, + attoAlphAmount = outputRefAmount, + tokens = outputRefTokens, + contractInput = contractInput + ) +} + final case class InputEntity( blockHash: BlockHash, txHash: TransactionId, @@ -38,8 +60,9 @@ final case class InputEntity( outputRefTxHash: Option[TransactionId], outputRefAddress: Option[Address], outputRefAmount: Option[U256], - outputRefTokens: Option[ArraySeq[Token]] // None if empty list -) { + outputRefTokens: Option[ArraySeq[Token]], // None if empty list + contractInput: Boolean +) extends InputEntityLike { /** @return All hash types associated with this [[InputEntity]] */ def hashes(): (TransactionId, BlockHash) = diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/OutputEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/OutputEntity.scala index 1af76972b..2754a8d63 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/OutputEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/OutputEntity.scala @@ -20,11 +20,52 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString -import org.alephium.explorer.api.model.Token +import org.alephium.explorer.api.model.{AssetOutput, ContractOutput, Output, Token} import org.alephium.protocol.Hash import org.alephium.protocol.model.{Address, BlockHash, TransactionId} import org.alephium.util.{TimeStamp, U256} +trait OutputEntityLike { + + def outputType: OutputEntity.OutputType + def hint: Int + def key: Hash + def amount: U256 + def address: Address + def tokens: Option[ArraySeq[Token]] + def lockTime: Option[TimeStamp] + def message: Option[ByteString] + def spentFinalized: Option[TransactionId] + def fixedOutput: Boolean + + def toApi(): Output = + outputType match { + case OutputEntity.Asset => + AssetOutput( + hint = hint, + key = key, + attoAlphAmount = amount, + address = address, + tokens = tokens, + lockTime = lockTime, + message = message, + spent = spentFinalized, + fixedOutput = fixedOutput + ) + + case OutputEntity.Contract => + ContractOutput( + hint = hint, + key = key, + attoAlphAmount = amount, + address = address, + tokens = tokens, + spent = spentFinalized, + fixedOutput = fixedOutput + ) + } +} + final case class OutputEntity( blockHash: BlockHash, txHash: TransactionId, @@ -42,8 +83,9 @@ final case class OutputEntity( txOrder: Int, coinbase: Boolean, spentFinalized: Option[TransactionId], - spentTimestamp: Option[TimeStamp] -) + spentTimestamp: Option[TimeStamp], + fixedOutput: Boolean +) extends OutputEntityLike object OutputEntity { sealed trait OutputType { diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/TransactionEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/TransactionEntity.scala index 6342e6e7b..45c17a1d5 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/TransactionEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/TransactionEntity.scala @@ -20,6 +20,7 @@ import scala.collection.immutable.ArraySeq import akka.util.ByteString +import org.alephium.explorer.api.model.{Input, Output, Transaction} import org.alephium.protocol.model.{BlockHash, GroupIndex, TransactionId} import org.alephium.util.{TimeStamp, U256} @@ -29,6 +30,9 @@ final case class TransactionEntity( timestamp: TimeStamp, chainFrom: GroupIndex, chainTo: GroupIndex, + version: Byte, + networkId: Byte, + scriptOpt: Option[String], gasAmount: Int, gasPrice: U256, order: Int, @@ -37,4 +41,22 @@ final case class TransactionEntity( inputSignatures: Option[ArraySeq[ByteString]], scriptSignatures: Option[ArraySeq[ByteString]], coinbase: Boolean -) +) { + def toApi(inputs: ArraySeq[Input], outputs: ArraySeq[Output]): Transaction = + Transaction( + hash, + blockHash, + timestamp, + inputs, + outputs, + version, + networkId, + scriptOpt, + gasAmount, + gasPrice, + scriptExecutionOk, + inputSignatures.getOrElse(ArraySeq.empty), + scriptSignatures.getOrElse(ArraySeq.empty), + coinbase + ) +} diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/UInputEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/UInputEntity.scala index 54219ecde..3231d61ee 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/UInputEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/UInputEntity.scala @@ -37,6 +37,7 @@ final case class UInputEntity( None, address, None, - None + None, + contractInput = false ) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/model/UOutputEntity.scala b/app/src/main/scala/org/alephium/explorer/persistence/model/UOutputEntity.scala index dcf2edc50..fe4eee20b 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/model/UOutputEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/model/UOutputEntity.scala @@ -45,6 +45,7 @@ final case class UOutputEntity( tokens, lockTime, message, - None + None, + fixedOutput = true ) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockDepQueries.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockDepQueries.scala deleted file mode 100644 index 8744ed6a7..000000000 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockDepQueries.scala +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 The Alephium Authors -// This file is part of the alephium project. -// -// The library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the library. If not, see . - -package org.alephium.explorer.persistence.queries - -import slick.jdbc.{PositionedParameters, SetParameter, SQLActionBuilder} -import slick.jdbc.PostgresProfile.api._ - -import org.alephium.explorer.persistence.DBActionW -import org.alephium.explorer.persistence.model.BlockDepEntity -import org.alephium.explorer.persistence.schema.CustomGetResult._ -import org.alephium.explorer.persistence.schema.CustomSetParameter._ -import org.alephium.explorer.util.SlickUtil._ -import org.alephium.protocol.model.BlockHash - -object BlockDepQueries { - - @SuppressWarnings(Array("org.wartremover.warts.PublicInference")) - def getDepsForBlock(blockHash: BlockHash) = { - sql""" - SELECT dep - FROM block_deps - WHERE hash = $blockHash - ORDER BY dep_order - """.asAS[BlockHash] - } - - /** Insert block_deps or ignore if there is a primary key conflict. - * - * Slick creates the following `INSERT` using string interpolation. Here the same is achieved by - * manually creating the [[slick.jdbc.SQLActionBuilder]] so our inserts can write multiple rows - * within a single `INSERT` statement. - * - * Splicing is - * not used to insert values so these queries are still cacheable prepared-statements. - */ - def insertBlockDeps(deps: Iterable[BlockDepEntity]): DBActionW[Int] = - // generate '?' placeholders for the parameterised SQL query - QuerySplitter.splitUpdates(rows = deps, columnsPerRow = 3) { (deps, placeholder) => - val query = - s""" - |INSERT INTO block_deps ("hash", "dep", "dep_order") - |VALUES $placeholder - |ON CONFLICT ON CONSTRAINT hash_deps_pk - | DO NOTHING - |""".stripMargin - - // set parameters following the insert order defined by the query above - val parameters: SetParameter[Unit] = - (_: Unit, params: PositionedParameters) => - deps foreach { dep => - params >> dep.hash - params >> dep.dep - params >> dep.order - } - - // Return builder generated by Slick's string interpolation - SQLActionBuilder( - sql = query, - setParameter = parameters - ).asUpdate - } -} diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockQueries.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockQueries.scala index f880cd885..37c6f4948 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockQueries.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/BlockQueries.scala @@ -29,7 +29,6 @@ import org.alephium.explorer.GroupSetting import org.alephium.explorer.api.model._ import org.alephium.explorer.persistence._ import org.alephium.explorer.persistence.model._ -import org.alephium.explorer.persistence.queries.BlockDepQueries._ import org.alephium.explorer.persistence.queries.InputQueries.insertInputs import org.alephium.explorer.persistence.queries.OutputQueries.insertOutputs import org.alephium.explorer.persistence.queries.TransactionQueries._ @@ -61,20 +60,21 @@ object BlockQueries extends StrictLogging { ) } - def buildBlockEntryAction( - blockHeader: BlockHeader - )(implicit ec: ExecutionContext): DBActionR[BlockEntry] = - for { - deps <- getDepsForBlock(blockHeader.hash) - txs <- getTransactionsByBlockHash(blockHeader.hash) - } yield blockHeader.toApi(deps, txs) - def getBlockEntryLiteAction( hash: BlockHash - )(implicit ec: ExecutionContext): DBActionR[Option[BlockEntryLite]] = - for { - header <- BlockHeaderSchema.table.filter(_.hash === hash).result.headOption - } yield header.map(_.toLiteApi) + ): DBActionR[Option[BlockEntryLite]] = + sql""" + select hash, + block_timestamp, + chain_from, + chain_to, + height, + main_chain, + hashrate, + txs_count + from #$block_headers + where hash = $hash + """.asASE[BlockEntryLite](blockEntryListGetResult).headOption /** For a given `BlockHash` returns its basic chain information */ def getBlockChainInfo(hash: BlockHash): DBActionR[Option[(GroupIndex, GroupIndex, Boolean)]] = @@ -93,8 +93,7 @@ object BlockQueries extends StrictLogging { )(implicit ec: ExecutionContext): DBActionR[Option[BlockEntry]] = for { headers <- BlockHeaderSchema.table.filter(_.hash === hash).result - blocks <- DBIOAction.sequence(headers.map(buildBlockEntryAction)) - } yield blocks.headOption + } yield headers.headOption.map(_.toApi()) def getBlockHeaderAction(hash: BlockHash): DBActionR[Option[BlockHeader]] = sql""" @@ -152,8 +151,7 @@ object BlockQueries extends StrictLogging { ): DBActionR[ArraySeq[BlockEntry]] = for { headers <- getHeadersAtHeightQuery(fromGroup, toGroup, height) - blocks <- DBIOAction.sequence(headers.map(buildBlockEntryAction)) - } yield blocks + } yield headers.map(_.toApi()) /** Order by query for [[org.alephium.explorer.persistence.schema.BlockHeaderSchema.table]] */ @@ -268,21 +266,6 @@ object BlockQueries extends StrictLogging { ).asUpdate } - def buildBlockEntryWithoutTxsAction( - blockHeader: BlockHeader - )(implicit ec: ExecutionContext): DBActionR[BlockEntry] = - for { - deps <- getDepsForBlock(blockHeader.hash) - } yield blockHeader.toApi(deps, ArraySeq.empty) - - def getBlockEntryWithoutTxsAction( - hash: BlockHash - )(implicit ec: ExecutionContext): DBActionR[Option[BlockEntry]] = - for { - headers <- BlockHeaderSchema.table.filter(_.hash === hash).result - blocks <- DBIOAction.sequence(headers.map(buildBlockEntryWithoutTxsAction)) - } yield blocks.headOption - def getLatestBlock(chainFrom: GroupIndex, chainTo: GroupIndex): DBActionR[Option[LatestBlock]] = { LatestBlockSchema.table .filter { block => @@ -295,10 +278,10 @@ object BlockQueries extends StrictLogging { /** Inserts block_headers or ignore them if there is a primary key conflict */ // scalastyle:off magic.number def insertBlockHeaders(blocks: Iterable[BlockHeader]): DBActionW[Int] = - QuerySplitter.splitUpdates(rows = blocks, columnsPerRow = 14) { (blocks, placeholder) => + QuerySplitter.splitUpdates(rows = blocks, columnsPerRow = 16) { (blocks, placeholder) => val query = s""" - insert into $block_headers ("hash", + INSERT INTO $block_headers ("hash", "block_timestamp", "chain_from", "chain_to", @@ -311,8 +294,10 @@ object BlockQueries extends StrictLogging { "txs_count", "target", "hashrate", - "parent") - values $placeholder + "parent", + "deps", + "ghost_uncles") + VALUES $placeholder ON CONFLICT ON CONSTRAINT block_headers_pkey DO NOTHING """ @@ -334,6 +319,8 @@ object BlockQueries extends StrictLogging { params >> block.target params >> block.hashrate params >> block.parent + params >> block.deps + params >> block.ghostUncles } SQLActionBuilder( @@ -348,7 +335,6 @@ object BlockQueries extends StrictLogging { Array("org.wartremover.warts.MutableDataStructures", "org.wartremover.warts.NonUnitStatements") ) def insertBlockEntity(blocks: Iterable[BlockEntity], groupNum: Int): DBActionRWT[Unit] = { - val blockDeps = ListBuffer.empty[BlockDepEntity] val transactions = ListBuffer.empty[TransactionEntity] val inputs = ListBuffer.empty[InputEntity] val outputs = ListBuffer.empty[OutputEntity] @@ -356,7 +342,6 @@ object BlockQueries extends StrictLogging { // build data for all insert queries in single iteration blocks foreach { block => - if (block.height.value != 0) blockDeps addAll block.toBlockDepEntities() transactions addAll block.transactions inputs addAll block.inputs outputs addAll block.outputs @@ -365,7 +350,6 @@ object BlockQueries extends StrictLogging { val query = DBIOAction.seq( - insertBlockDeps(blockDeps), insertTransactions(transactions), insertOutputs(outputs), insertInputs(inputs), diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/InputQueries.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/InputQueries.scala index 0d59ae5e4..65064dc59 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/InputQueries.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/InputQueries.scala @@ -38,7 +38,7 @@ object InputQueries { /** Inserts inputs or ignore rows with primary key conflict */ // scalastyle:off magic.number def insertInputs(inputs: Iterable[InputEntity]): DBActionW[Int] = - QuerySplitter.splitUpdates(rows = inputs, columnsPerRow = 12) { (inputs, placeholder) => + QuerySplitter.splitUpdates(rows = inputs, columnsPerRow = 13) { (inputs, placeholder) => val query = s""" INSERT INTO inputs ("block_hash", @@ -52,7 +52,8 @@ object InputQueries { "tx_order", "output_ref_tx_hash", "output_ref_address", - "output_ref_amount") + "output_ref_amount", + "contract_input") VALUES $placeholder ON CONFLICT ON CONSTRAINT inputs_pk @@ -74,6 +75,7 @@ object InputQueries { params >> input.outputRefTxHash params >> input.outputRefAddress params >> input.outputRefAmount + params >> input.contractInput } SQLActionBuilder( @@ -98,15 +100,7 @@ object InputQueries { val query = s""" - SELECT tx_hash, - input_order, - hint, - output_ref_key, - unlock_script, - output_ref_tx_hash, - output_ref_address, - output_ref_amount, - output_ref_tokens + SELECT ${InputsFromTxQR.selectFields} FROM inputs WHERE (tx_hash, block_hash) IN $params @@ -127,13 +121,7 @@ object InputQueries { def getInputsQuery(txHash: TransactionId, blockHash: BlockHash): DBActionSR[InputsQR] = sql""" - SELECT hint, - output_ref_key, - unlock_script, - output_ref_tx_hash, - output_ref_address, - output_ref_amount, - output_ref_tokens + SELECT #${InputsQR.selectFields} FROM inputs WHERE tx_hash = $txHash AND block_hash = $blockHash @@ -155,7 +143,8 @@ object InputQueries { output_ref_tx_hash, output_ref_address, output_ref_amount, - output_ref_tokens + output_ref_tokens, + contract_input FROM inputs WHERE main_chain = true ORDER BY block_timestamp #${if (ascendingOrder) "" else "DESC"} diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/OutputQueries.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/OutputQueries.scala index 6ca8451f2..c1d8ac45c 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/OutputQueries.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/OutputQueries.scala @@ -50,7 +50,7 @@ object OutputQueries { // scalastyle:off magic.number method.length private def insertBasicOutputs(outputs: Iterable[OutputEntity]): DBActionW[Int] = QuerySplitter - .splitUpdates(rows = outputs, columnsPerRow = 17) { (outputs, placeholder) => + .splitUpdates(rows = outputs, columnsPerRow = 18) { (outputs, placeholder) => val query = s""" INSERT INTO outputs ("block_hash", @@ -69,7 +69,8 @@ object OutputQueries { "tx_order", "coinbase", "spent_finalized", - "spent_timestamp") + "spent_timestamp", + "fixed_output") VALUES $placeholder ON CONFLICT ON CONSTRAINT outputs_pk @@ -96,6 +97,7 @@ object OutputQueries { params >> output.coinbase params >> output.spentFinalized params >> output.spentTimestamp + params >> output.fixedOutput } SQLActionBuilder( @@ -316,17 +318,7 @@ object OutputQueries { val query = s""" - SELECT outputs.tx_hash, - outputs.output_order, - outputs.output_type, - outputs.hint, - outputs.key, - outputs.amount, - outputs.address, - outputs.tokens, - outputs.lock_time, - outputs.message, - outputs.spent_finalized + SELECT ${OutputsFromTxQR.selectFields} FROM outputs WHERE (outputs.tx_hash, outputs.block_hash) IN $params """ @@ -348,15 +340,7 @@ object OutputQueries { def getOutputsQuery(txHash: TransactionId, blockHash: BlockHash): DBActionSR[OutputsQR] = sql""" - SELECT output_type, - hint, - key, - amount, - address, - tokens, - lock_time, - message, - spent_finalized + SELECT #${OutputsQR.selectFields} FROM outputs WHERE tx_hash = $txHash AND block_hash = $blockHash @@ -383,7 +367,9 @@ object OutputQueries { output_order, tx_order, coinbase, - spent_finalized + spent_finalized, + spent_timestamp, + fixed_output FROM outputs WHERE main_chain = true ORDER BY block_timestamp #${if (ascendingOrder) "" else "DESC"} diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/TransactionQueries.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/TransactionQueries.scala index 02d45641c..a7b6d31fd 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/TransactionQueries.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/TransactionQueries.scala @@ -55,9 +55,9 @@ object TransactionQueries extends StrictLogging { } /** Inserts transactions or ignore rows with primary key conflict */ - // scalastyle:off magic.number + // scalastyle:off magic.number method.length def insertTransactions(transactions: Iterable[TransactionEntity]): DBActionW[Int] = - QuerySplitter.splitUpdates(rows = transactions, columnsPerRow = 13) { + QuerySplitter.splitUpdates(rows = transactions, columnsPerRow = 16) { (transactions, placeholder) => val query = s""" @@ -66,6 +66,9 @@ object TransactionQueries extends StrictLogging { block_timestamp, chain_from, chain_to, + version, + network_id, + script_opt, gas_amount, gas_price, tx_order, @@ -87,6 +90,9 @@ object TransactionQueries extends StrictLogging { params >> transaction.timestamp params >> transaction.chainFrom params >> transaction.chainTo + params >> transaction.version + params >> transaction.networkId + params >> transaction.scriptOpt params >> transaction.gasAmount params >> transaction.gasPrice params >> transaction.order @@ -113,37 +119,25 @@ object TransactionQueries extends StrictLogging { private val getTransactionQuery = Compiled { (txHash: Rep[TransactionId]) => mainTransactions .filter(_.hash === txHash) - .map(tx => - (tx.blockHash, tx.timestamp, tx.gasAmount, tx.gasPrice, tx.scriptExecutionOk, tx.coinbase) - ) } def getTransactionAction( txHash: TransactionId )(implicit ec: ExecutionContext): DBActionR[Option[Transaction]] = getTransactionQuery(txHash).result.headOption.flatMap { - case None => DBIOAction.successful(None) - case Some((blockHash, timestamp, gasAmount, gasPrice, scriptExecutionOk, coinbase)) => - getKnownTransactionAction( - txHash, - blockHash, - timestamp, - gasAmount, - gasPrice, - scriptExecutionOk, - coinbase - ).map(Some.apply) + case None => DBIOAction.successful(None) + case Some(tx) => getKnownTransactionAction(tx).map(Some.apply) } private def getTxHashesByBlockHashQuery( blockHash: BlockHash - ): DBActionSR[(TransactionId, BlockHash, TimeStamp, Int, Boolean)] = + ): DBActionSR[TxByAddressQR] = sql""" SELECT hash, block_hash, block_timestamp, tx_order, coinbase FROM transactions WHERE block_hash = $blockHash ORDER BY tx_order - """.asAS[(TransactionId, BlockHash, TimeStamp, Int, Boolean)] + """.asAS[TxByAddressQR] private def getTxHashesByBlockHashWithPaginationQuery( blockHash: BlockHash, @@ -156,7 +150,7 @@ object TransactionQueries extends StrictLogging { ORDER BY tx_order """ .paginate(pagination) - .asAS[(TransactionId, BlockHash, TimeStamp, Int, Boolean)] + .asAS[TxByAddressQR] def countAddressTransactions(address: Address): DBActionSR[Int] = { sql""" @@ -257,7 +251,7 @@ object TransactionQueries extends StrictLogging { )(implicit ec: ExecutionContext): DBActionSR[Transaction] = { for { txHashesTs <- getTxHashesByBlockHashQuery(blockHash) - txs <- getTransactions(TxByAddressQR(txHashesTs)) + txs <- getTransactions(txHashesTs) } yield txs } @@ -266,7 +260,7 @@ object TransactionQueries extends StrictLogging { ): DBActionR[ArraySeq[Transaction]] = { for { txHashesTs <- getTxHashesByBlockHashWithPaginationQuery(blockHash, pagination) - txs <- getTransactions(TxByAddressQR(txHashesTs)) + txs <- getTransactions(txHashesTs) } yield txs } @@ -431,28 +425,33 @@ object TransactionQueries extends StrictLogging { val insByTx = inputs.groupBy(_.txHash).view.mapValues { values => values .sortBy(_.inputOrder) - .map(_.toApiInput()) + .map(_.toApi()) } val ousByTx = outputs.groupBy(_.txHash).view.mapValues { values => values .sortBy(_.outputOrder) - .map(_.toApiOutput()) + .map(_.toApi()) } - val gasByTx = gases.groupBy(_.txHash).view.mapValues(_.map(_.info())) + val gasByTx = gases.groupBy(_.txHash) txHashesTs.map { txn => - val ins = insByTx.getOrElse(txn.txHash, ArraySeq.empty) - val ous = ousByTx.getOrElse(txn.txHash, ArraySeq.empty) - val gas = gasByTx.getOrElse(txn.txHash, ArraySeq.empty) - val (gasAmount, gasPrice, scriptExecutionOk) = gas.headOption.getOrElse((0, U256.Zero, true)) + val ins = insByTx.getOrElse(txn.txHash, ArraySeq.empty) + val ous = ousByTx.getOrElse(txn.txHash, ArraySeq.empty) + val gas = gasByTx.getOrElse(txn.txHash, ArraySeq.empty) + val info = gas.headOption.getOrElse(InfoFromTxsQR.empty()) Transaction( txn.txHash, txn.blockHash, txn.blockTimestamp, ins, ous, - gasAmount, - gasPrice, - scriptExecutionOk, + info.version, + info.networkId, + info.scriptOpt, + info.gasAmount, + info.gasPrice, + info.scriptExecutionOk, + info.inputSignatures.getOrElse(ArraySeq.empty), + info.scriptSignatures.getOrElse(ArraySeq.empty), txn.coinbase ) } @@ -462,7 +461,7 @@ object TransactionQueries extends StrictLogging { if (hashes.nonEmpty) { val params = paramPlaceholderTuple2(1, hashes.size) val query = s""" - SELECT hash, gas_amount, gas_price, script_execution_ok + SELECT ${InfoFromTxsQR.selectFields} FROM transactions WHERE (hash, block_hash) IN $params """ @@ -483,28 +482,27 @@ object TransactionQueries extends StrictLogging { } private def getKnownTransactionAction( - txHash: TransactionId, - blockHash: BlockHash, - timestamp: TimeStamp, - gasAmount: Int, - gasPrice: U256, - scriptExecutionOk: Boolean, - coinbase: Boolean + tx: TransactionEntity )(implicit ec: ExecutionContext): DBActionR[Transaction] = for { - ins <- getInputsQuery(txHash, blockHash) - outs <- getOutputsQuery(txHash, blockHash) + ins <- getInputsQuery(tx.hash, tx.blockHash) + outs <- getOutputsQuery(tx.hash, tx.blockHash) } yield { Transaction( - txHash, - blockHash, - timestamp, - ins.map(_.toApiInput()), - outs.map(_.toApiOutput()), - gasAmount, - gasPrice, - scriptExecutionOk, - coinbase + tx.hash, + tx.blockHash, + tx.timestamp, + ins.map(_.toApi()), + outs.map(_.toApi()), + tx.version, + tx.networkId, + tx.scriptOpt, + tx.gasAmount, + tx.gasPrice, + tx.scriptExecutionOk, + tx.inputSignatures.getOrElse(ArraySeq.empty), + tx.scriptSignatures.getOrElse(ArraySeq.empty), + tx.coinbase ) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/InfoFromTxsQR.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/InfoFromTxsQR.scala index 8cbe4ae17..f462019f8 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/InfoFromTxsQR.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/InfoFromTxsQR.scala @@ -16,6 +16,9 @@ package org.alephium.explorer.persistence.queries.result +import scala.collection.immutable.ArraySeq + +import akka.util.ByteString import slick.jdbc.{GetResult, PositionedResult} import org.alephium.explorer.persistence.schema.CustomGetResult._ @@ -24,26 +27,50 @@ import org.alephium.util.U256 object InfoFromTxsQR { + val selectFields: String = + "hash, version, network_id, script_opt, gas_amount, gas_price, script_execution_ok, input_signatures, script_signatures" + implicit val infoFromTxsQRGetResult: GetResult[InfoFromTxsQR] = (result: PositionedResult) => InfoFromTxsQR( txHash = result.<<, + version = result.<<, + networkId = result.<<, + scriptOpt = result.< InputsFromTxQR( @@ -36,10 +41,11 @@ object InputsFromTxQR { hint = result.<<, outputRefKey = result.<<, unlockScript = result.< InputsQR( @@ -37,7 +42,8 @@ object InputsQR { outputRefTxHash = result.< OutputsFromTxQR( @@ -42,7 +46,8 @@ object OutputsFromTxQR { tokens = result.< - AssetOutput( - hint = hint, - key = key, - attoAlphAmount = amount, - address = address, - tokens = tokens, - lockTime = lockTime, - message = message, - spent = spent - ) - - case OutputEntity.Contract => - ContractOutput( - hint = hint, - key = key, - attoAlphAmount = amount, - address = address, - tokens = tokens, - spent = spent - ) - } -} + spentFinalized: Option[TransactionId], + fixedOutput: Boolean +) extends OutputEntityLike diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/OutputsQR.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/OutputsQR.scala index 4d2b675e3..03ed37fd3 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/OutputsQR.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/OutputsQR.scala @@ -22,13 +22,17 @@ import akka.util.ByteString import slick.jdbc.{GetResult, PositionedResult} import org.alephium.explorer.api.model._ -import org.alephium.explorer.persistence.model.OutputEntity +import org.alephium.explorer.persistence.model.{OutputEntity, OutputEntityLike} import org.alephium.explorer.persistence.schema.CustomGetResult._ import org.alephium.protocol.Hash import org.alephium.protocol.model.{Address, TransactionId} import org.alephium.util.{TimeStamp, U256} object OutputsQR { + + val selectFields: String = + "output_type, hint, key, amount, address, tokens, lock_time, message, spent_finalized, fixed_output" + implicit val outputsQRGetResult: GetResult[OutputsQR] = (result: PositionedResult) => OutputsQR( @@ -40,7 +44,8 @@ object OutputsQR { tokens = result.< - AssetOutput( - hint = hint, - key = key, - attoAlphAmount = amount, - address = address, - tokens = tokens, - lockTime = lockTime, - message = message, - spent = spentFinalized - ) - - case OutputEntity.Contract => - ContractOutput( - hint = hint, - key = key, - attoAlphAmount = amount, - address = address, - tokens = tokens, - spent = spentFinalized - ) - } -} + spentFinalized: Option[TransactionId], + fixedOutput: Boolean +) extends OutputEntityLike diff --git a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/TxByTokenQR.scala b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/TxByTokenQR.scala index ab2c33d73..fa07f73b4 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/queries/result/TxByTokenQR.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/queries/result/TxByTokenQR.scala @@ -26,7 +26,8 @@ import org.alephium.util.TimeStamp object TxByTokenQR { - val selectFields = "tx_hash, block_hash, block_timestamp, tx_order" + val selectFields: String = + "tx_hash, block_hash, block_timestamp, tx_order" private type Tuple = (TransactionId, BlockHash, TimeStamp, Int, Boolean) diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockDepsSchema.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockDepsSchema.scala deleted file mode 100644 index 124e3bd45..000000000 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockDepsSchema.scala +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018 The Alephium Authors -// This file is part of the alephium project. -// -// The library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the library. If not, see . - -package org.alephium.explorer.persistence.schema - -import slick.jdbc.PostgresProfile.api._ -import slick.lifted.{Index, PrimaryKey, ProvenShape} - -import org.alephium.explorer.persistence.model.BlockDepEntity -import org.alephium.explorer.persistence.schema.CustomJdbcTypes._ -import org.alephium.protocol.model.BlockHash - -object BlockDepsSchema extends Schema[BlockDepEntity]("block_deps") { - - class BlockDeps(tag: Tag) extends Table[BlockDepEntity](tag, name) { - def hash: Rep[BlockHash] = column[BlockHash]("hash", O.SqlType("BYTEA")) - def dep: Rep[BlockHash] = column[BlockHash]("dep", O.SqlType("BYTEA")) - def depOrder: Rep[Int] = column[Int]("dep_order") - - def pk: PrimaryKey = primaryKey("hash_deps_pk", (hash, dep)) - def depIdx: Index = index("deps_dep_idx", dep) - - def * : ProvenShape[BlockDepEntity] = - (hash, dep, depOrder).<>((BlockDepEntity.apply _).tupled, BlockDepEntity.unapply) - } - - val table: TableQuery[BlockDeps] = TableQuery[BlockDeps] -} diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockHeaderSchema.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockHeaderSchema.scala index 5ee5d7e18..bea9ccce6 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockHeaderSchema.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/BlockHeaderSchema.scala @@ -18,12 +18,14 @@ package org.alephium.explorer.persistence.schema import java.math.BigInteger +import scala.collection.immutable.ArraySeq + import akka.util.ByteString import slick.jdbc.PostgresProfile.api._ import slick.lifted.{Index, ProvenShape} import slick.sql.SqlAction -import org.alephium.explorer.api.model.Height +import org.alephium.explorer.api.model.{GhostUncle, Height} import org.alephium.explorer.persistence.model.BlockHeader import org.alephium.explorer.persistence.schema.CustomJdbcTypes._ import org.alephium.protocol.Hash @@ -52,6 +54,9 @@ object BlockHeaderSchema extends SchemaMainChain[BlockHeader]("block_headers") { O.SqlType("DECIMAL(80,0)") ) // TODO How much decimal we need? this one is the same as for U256 def parent: Rep[Option[BlockHash]] = column[Option[BlockHash]]("parent") + def deps: Rep[ArraySeq[BlockHash]] = column[ArraySeq[BlockHash]]("deps") + def ghostUncles: Rep[Option[ArraySeq[GhostUncle]]] = + column[Option[ArraySeq[GhostUncle]]]("ghost_uncles") def timestampIdx: Index = index("blocks_timestamp_idx", timestamp) def heightIdx: Index = index("blocks_height_idx", height) @@ -71,7 +76,9 @@ object BlockHeaderSchema extends SchemaMainChain[BlockHeader]("block_headers") { txsCount, target, hashrate, - parent + parent, + deps, + ghostUncles ) .<>((BlockHeader.apply _).tupled, BlockHeader.unapply) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomGetResult.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomGetResult.scala index 98f534b51..6a325f71c 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomGetResult.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomGetResult.scala @@ -100,6 +100,17 @@ object CustomGetResult { (result: PositionedResult) => result.nextBytesOption().map(bytes => ByteString.fromArrayUnsafe(bytes)) + implicit val optionByteStringsGetResult: GetResult[Option[ArraySeq[ByteString]]] = + (result: PositionedResult) => + result + .nextBytesOption() + .map { bytes => + deserialize[ArraySeq[ByteString]](ByteString.fromArrayUnsafe(bytes)) match { + case Left(error) => throw error + case Right(value) => value + } + } + implicit val optionTokensGetResult: GetResult[Option[ArraySeq[Token]]] = (result: PositionedResult) => result @@ -111,6 +122,13 @@ object CustomGetResult { } } + implicit val blockHashesGetResult: GetResult[ArraySeq[BlockHash]] = + (result: PositionedResult) => + deserialize[ArraySeq[BlockHash]](ByteString.fromArrayUnsafe(result.nextBytes())) match { + case Left(error) => throw error + case Right(value) => value + } + implicit val valsGetResult: GetResult[ArraySeq[Val]] = (result: PositionedResult) => readBinary[ArraySeq[Val]](result.nextBytes()) @@ -165,7 +183,8 @@ object CustomGetResult { txOrder = result.<<, coinbase = result.<<, spentFinalized = result.< result.nextStringOption().map(InterfaceIdEntity.from) + implicit val optionGhostUnclesGetResult: GetResult[Option[ArraySeq[GhostUncle]]] = + (result: PositionedResult) => + result.nextBytesOption().map(bytes => readBinary[ArraySeq[GhostUncle]](bytes)) + /** GetResult type for BlockEntryLite * * @note @@ -231,7 +255,9 @@ object CustomGetResult { txsCount = result.<<, target = result.<<, hashrate = result.<<, - parent = result.< serialize(hashes).toArray, + bytes => + deserialize[ArraySeq[BlockHash]](ByteString.fromArrayUnsafe(bytes)) match { + case Left(error) => throw error + case Right(value) => value + } + ) + implicit val valsType: JdbcType[ArraySeq[Val]] = MappedJdbcType.base[ArraySeq[Val], Array[Byte]]( vals => writeBinary(vals), @@ -174,4 +184,10 @@ object CustomJdbcTypes { .find(_.key == key) .getOrElse(throw new Exception(s"Invalid ${classOf[AppStateKey[_]].getSimpleName}: $key")) ) + + implicit val ghostUnclesType: JdbcType[ArraySeq[GhostUncle]] = + MappedJdbcType.base[ArraySeq[GhostUncle], Array[Byte]]( + uncles => writeBinary(uncles), + bytes => readBinary[ArraySeq[GhostUncle]](bytes) + ) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomSetParameter.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomSetParameter.scala index e6f974dd8..ab25b08e3 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomSetParameter.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/CustomSetParameter.scala @@ -176,6 +176,11 @@ object CustomSetParameter { } } + implicit object BlockHashesStringsOptionSetParameter extends SetParameter[ArraySeq[BlockHash]] { + override def apply(input: ArraySeq[BlockHash], params: PositionedParameters): Unit = + params setBytes serialize(input).toArray + } + implicit object ValsSetParameter extends SetParameter[ArraySeq[Val]] { override def apply(input: ArraySeq[Val], params: PositionedParameters): Unit = params setBytes writeBinary(input) @@ -255,4 +260,22 @@ object CustomSetParameter { params setTimestampOption None } } + + implicit object GhostUnclesSetParameter extends SetParameter[ArraySeq[GhostUncle]] { + override def apply(input: ArraySeq[GhostUncle], params: PositionedParameters): Unit = + params setBytes writeBinary(input) + } + + implicit object GhostUnclesOptionSetParameter extends SetParameter[Option[ArraySeq[GhostUncle]]] { + override def apply(option: Option[ArraySeq[GhostUncle]], params: PositionedParameters): Unit = + option match { + case Some(ghostUncles) => + GhostUnclesSetParameter(ghostUncles, params) + + case None => + // scalastyle:off null + params setBytes null + // scalastyle:on null + } + } } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/InputSchema.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/InputSchema.scala index 88093f0c3..443f5f93b 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/InputSchema.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/InputSchema.scala @@ -52,6 +52,7 @@ object InputSchema extends SchemaMainChain[InputEntity]("inputs") { ) // U256.MaxValue has 78 digits def outputRefTokens: Rep[Option[ArraySeq[Token]]] = column[Option[ArraySeq[Token]]]("output_ref_tokens") + def contractInput: Rep[Boolean] = column[Boolean]("contract_input") def pk: PrimaryKey = primaryKey("inputs_pk", (outputRefKey, blockHash)) @@ -75,7 +76,8 @@ object InputSchema extends SchemaMainChain[InputEntity]("inputs") { outputRefTxHash, outputRefAddress, outputRefAmount, - outputRefTokens + outputRefTokens, + contractInput ) .<>((InputEntity.apply _).tupled, InputEntity.unapply) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/OutputSchema.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/OutputSchema.scala index 8c6707889..0778716de 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/OutputSchema.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/OutputSchema.scala @@ -52,6 +52,7 @@ object OutputSchema extends SchemaMainChain[OutputEntity]("outputs") { def spentFinalized: Rep[Option[TransactionId]] = column[Option[TransactionId]]("spent_finalized", O.Default(None)) def spentTimestamp: Rep[Option[TimeStamp]] = column[Option[TimeStamp]]("spent_timestamp") + def fixedOutput: Rep[Boolean] = column[Boolean]("fixed_output") def pk: PrimaryKey = primaryKey("outputs_pk", (key, blockHash)) @@ -80,7 +81,8 @@ object OutputSchema extends SchemaMainChain[OutputEntity]("outputs") { txOrder, coinbase, spentFinalized, - spentTimestamp + spentTimestamp, + fixedOutput ) .<>((OutputEntity.apply _).tupled, OutputEntity.unapply) } diff --git a/app/src/main/scala/org/alephium/explorer/persistence/schema/TransactionSchema.scala b/app/src/main/scala/org/alephium/explorer/persistence/schema/TransactionSchema.scala index d800074db..b80afe53f 100644 --- a/app/src/main/scala/org/alephium/explorer/persistence/schema/TransactionSchema.scala +++ b/app/src/main/scala/org/alephium/explorer/persistence/schema/TransactionSchema.scala @@ -30,12 +30,15 @@ import org.alephium.util.{TimeStamp, U256} object TransactionSchema extends SchemaMainChain[TransactionEntity]("transactions") { class Transactions(tag: Tag) extends Table[TransactionEntity](tag, name) { - def hash: Rep[TransactionId] = column[TransactionId]("hash", O.SqlType("BYTEA")) - def blockHash: Rep[BlockHash] = column[BlockHash]("block_hash", O.SqlType("BYTEA")) - def timestamp: Rep[TimeStamp] = column[TimeStamp]("block_timestamp") - def chainFrom: Rep[GroupIndex] = column[GroupIndex]("chain_from") - def chainTo: Rep[GroupIndex] = column[GroupIndex]("chain_to") - def gasAmount: Rep[Int] = column[Int]("gas_amount") + def hash: Rep[TransactionId] = column[TransactionId]("hash", O.SqlType("BYTEA")) + def blockHash: Rep[BlockHash] = column[BlockHash]("block_hash", O.SqlType("BYTEA")) + def timestamp: Rep[TimeStamp] = column[TimeStamp]("block_timestamp") + def chainFrom: Rep[GroupIndex] = column[GroupIndex]("chain_from") + def chainTo: Rep[GroupIndex] = column[GroupIndex]("chain_to") + def version: Rep[Byte] = column[Byte]("version") + def networkId: Rep[Byte] = column[Byte]("network_id") + def scriptOpt: Rep[Option[String]] = column[Option[String]]("script_opt") + def gasAmount: Rep[Int] = column[Int]("gas_amount") def gasPrice: Rep[U256] = column[U256]("gas_price", O.SqlType("DECIMAL(80,0)")) // U256.MaxValue has 78 digits def txOrder: Rep[Int] = column[Int]("tx_order") @@ -61,6 +64,9 @@ object TransactionSchema extends SchemaMainChain[TransactionEntity]("transaction timestamp, chainFrom, chainTo, + version, + networkId, + scriptOpt, gasAmount, gasPrice, txOrder, diff --git a/app/src/main/scala/org/alephium/explorer/service/BlockFlowClient.scala b/app/src/main/scala/org/alephium/explorer/service/BlockFlowClient.scala index 20ea89e07..8e7a3fcb0 100644 --- a/app/src/main/scala/org/alephium/explorer/service/BlockFlowClient.scala +++ b/app/src/main/scala/org/alephium/explorer/service/BlockFlowClient.scala @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the library. If not, see . +//scalastyle:off file.size.limit package org.alephium.explorer.service import java.math.BigInteger @@ -441,7 +442,16 @@ object BlockFlowClient extends StrictLogging { transactions.flatMap { case (tx, txOrder) => InputAddressUtil.convertSameAsPrevious(tx.unsigned.inputs.toArraySeq).zipWithIndex.map { case (in, index) => - inputToEntity(in, hash, tx.unsigned.txId, block.timestamp, mainChain, index, txOrder) + inputToEntity( + in, + hash, + tx.unsigned.txId, + block.timestamp, + mainChain, + index, + txOrder, + contractInput = false + ) } } val contractInputs = @@ -455,7 +465,8 @@ object BlockFlowClient extends StrictLogging { block.timestamp, mainChain, shiftIndex, - txOrder + txOrder, + contractInput = true ) } } @@ -482,7 +493,8 @@ object BlockFlowClient extends StrictLogging { block.timestamp, mainChain, txOrder, - txId == coinbaseTxId + coinbase = txId == coinbaseTxId, + fixedOutput = true ) } } @@ -498,7 +510,8 @@ object BlockFlowClient extends StrictLogging { block.timestamp, mainChain, txOrder, - false + coinbase = false, + fixedOutput = false ) } } @@ -531,6 +544,10 @@ object BlockFlowClient extends StrictLogging { // Genesis blocks don't have any transactions val coinbaseTxId = if (block.height == Height.genesis.value) null else block.transactions.last.unsigned.txId + val ghostUncles = block.ghostUncles.toArraySeq.map { ghostUncle => + GhostUncle(ghostUncle.blockHash, ghostUncle.miner) + } + BlockEntity( hash, block.timestamp, @@ -550,7 +567,8 @@ object BlockFlowClient extends StrictLogging { block.depStateHash, block.txsHash, block.target, - computeHashRate(block.target, block.timestamp) + computeHashRate(block.target, block.timestamp), + ghostUncles ) } // scalastyle:on null @@ -618,6 +636,9 @@ object BlockFlowClient extends StrictLogging { timestamp, chainFrom, chainTo, + tx.unsigned.version, + tx.unsigned.networkId, + tx.unsigned.scriptOpt.map(_.value), tx.unsigned.gasAmount, tx.unsigned.gasPrice, index, @@ -635,7 +656,8 @@ object BlockFlowClient extends StrictLogging { None, InputAddressUtil.addressFromProtocolInput(input), None, - None + None, + contractInput = false ) } @@ -646,7 +668,8 @@ object BlockFlowClient extends StrictLogging { timestamp: TimeStamp, mainChain: Boolean, index: Int, - txOrder: Int + txOrder: Int, + contractInput: Boolean ): InputEntity = { InputEntity( blockHash, @@ -661,7 +684,8 @@ object BlockFlowClient extends StrictLogging { None, InputAddressUtil.addressFromProtocolInput(input), None, - None + None, + contractInput = contractInput ) } @@ -672,7 +696,8 @@ object BlockFlowClient extends StrictLogging { timestamp: TimeStamp, mainChain: Boolean, index: Int, - txOrder: Int + txOrder: Int, + contractInput: Boolean ): InputEntity = { InputEntity( blockHash, @@ -687,7 +712,8 @@ object BlockFlowClient extends StrictLogging { None, None, None, - None + None, + contractInput = contractInput ) } @@ -704,7 +730,8 @@ object BlockFlowClient extends StrictLogging { protocolTokensToTokens(output.tokens), lockTime, Some(output.message), - None + None, + fixedOutput = true ) } @@ -735,18 +762,14 @@ object BlockFlowClient extends StrictLogging { timestamp: TimeStamp, mainChain: Boolean, txOrder: Int, - coinbase: Boolean + coinbase: Boolean, + fixedOutput: Boolean ): OutputEntity = { val lockTime = output match { case asset: api.model.AssetOutput if asset.lockTime.millis > 0 => Some(asset.lockTime) case _ => None } - val hint = output.address.lockupScript match { - case asset: LockupScript.Asset => Hint.ofAsset(asset.scriptHint) - case contract: LockupScript.P2C => Hint.ofContract(contract.scriptHint) - } - val outputType: OutputEntity.OutputType = output match { case _: api.model.AssetOutput => OutputEntity.Asset case _: api.model.ContractOutput => OutputEntity.Contract @@ -764,8 +787,8 @@ object BlockFlowClient extends StrictLogging { txId, timestamp, outputType, - hint.value, - protocol.model.TxOutputRef.key(txId, index).value, + output.hint, + output.key, output.attoAlphAmount.value, output.address, tokens, @@ -776,7 +799,8 @@ object BlockFlowClient extends StrictLogging { txOrder, coinbase, None, - None + None, + fixedOutput ) } diff --git a/app/src/main/scala/org/alephium/explorer/service/BlockFlowSyncService.scala b/app/src/main/scala/org/alephium/explorer/service/BlockFlowSyncService.scala index 3795cdfb6..e0dacdc97 100644 --- a/app/src/main/scala/org/alephium/explorer/service/BlockFlowSyncService.scala +++ b/app/src/main/scala/org/alephium/explorer/service/BlockFlowSyncService.scala @@ -344,6 +344,7 @@ case object BlockFlowSyncService extends StrictLogging { }).flatMap { _ => for { _ <- BlockDao.insertWithEvents(block, events) + _ <- handleUncles(block.ghostUncles.map(_.blockHash), block.chainFrom) _ <- BlockDao.updateMainChain( block.hash, block.chainFrom, @@ -381,4 +382,25 @@ case object BlockFlowSyncService extends StrictLogging { logger.debug(s"Downloading missing block $missing") blockFlowClient.fetchBlockAndEvents(chainFrom, missing).flatMap(insertWithEvents) } + + // Ghost uncle blocks are only insterted in the database, we don't update the main chain + private def handleUncles(uncles: ArraySeq[BlockHash], chainFrom: GroupIndex)(implicit + ec: ExecutionContext, + dc: DatabaseConfig[PostgresProfile], + blockFlowClient: BlockFlowClient, + groupSetting: GroupSetting + ): Future[Unit] = { + if (uncles.nonEmpty) { + logger.debug(s"Downloading ghost uncles ${uncles}") + Future + .sequence(uncles.map { uncle => + blockFlowClient + .fetchBlockAndEvents(chainFrom, uncle) + .flatMap(bwe => BlockDao.insertWithEvents(bwe.block, bwe.events)) + }) + .map(_ => ()) + } else { + Future.successful(()) + } + } } diff --git a/app/src/main/scala/org/alephium/explorer/service/BlockService.scala b/app/src/main/scala/org/alephium/explorer/service/BlockService.scala index 628ec33a7..93ebeafdb 100644 --- a/app/src/main/scala/org/alephium/explorer/service/BlockService.scala +++ b/app/src/main/scala/org/alephium/explorer/service/BlockService.scala @@ -29,6 +29,11 @@ import org.alephium.explorer.persistence.dao.BlockDao import org.alephium.protocol.model.BlockHash trait BlockService { + def getBlockByHash(hash: BlockHash)(implicit + ec: ExecutionContext, + dc: DatabaseConfig[PostgresProfile] + ): Future[Option[BlockEntry]] + def getLiteBlockByHash(hash: BlockHash)(implicit ec: ExecutionContext, dc: DatabaseConfig[PostgresProfile] @@ -59,6 +64,11 @@ trait BlockService { } object BlockService extends BlockService { + def getBlockByHash(hash: BlockHash)(implicit + ec: ExecutionContext, + dc: DatabaseConfig[PostgresProfile] + ): Future[Option[BlockEntry]] = + BlockDao.get(hash) def getLiteBlockByHash(hash: BlockHash)(implicit ec: ExecutionContext, diff --git a/app/src/main/scala/org/alephium/explorer/service/FlowEntity.scala b/app/src/main/scala/org/alephium/explorer/service/FlowEntity.scala index a30d6457d..429456095 100644 --- a/app/src/main/scala/org/alephium/explorer/service/FlowEntity.scala +++ b/app/src/main/scala/org/alephium/explorer/service/FlowEntity.scala @@ -18,8 +18,11 @@ package org.alephium.explorer.service import scala.collection.immutable.ArraySeq +import akka.util.ByteString + import org.alephium.explorer.AnyOps -import org.alephium.explorer.api.model.Height +import org.alephium.explorer.api.model.{GhostUncle, Height} +import org.alephium.protocol.Hash import org.alephium.protocol.model.{BlockHash, GroupIndex} import org.alephium.util.TimeStamp @@ -31,6 +34,12 @@ trait FlowEntity { def chainTo: GroupIndex def height: Height def deps: ArraySeq[BlockHash] + def nonce: ByteString + def version: Byte + def depStateHash: Hash + def txsHash: Hash + def target: ByteString + def ghostUncles: ArraySeq[GhostUncle] def mainChain: Boolean def parent(groupNum: Int): Option[BlockHash] = diff --git a/app/src/main/scala/org/alephium/explorer/service/SanityChecker.scala b/app/src/main/scala/org/alephium/explorer/service/SanityChecker.scala index 7065e7fc8..e196d5044 100644 --- a/app/src/main/scala/org/alephium/explorer/service/SanityChecker.scala +++ b/app/src/main/scala/org/alephium/explorer/service/SanityChecker.scala @@ -166,7 +166,7 @@ object SanityChecker extends StrictLogging { s"Checked $blockNum blocks , progress ${(nextBlockNum.toFloat / totalNbOfBlocks * 100.0).toInt}%" ) } - getBlockEntryWithoutTxsAction(hash) + getBlockEntryAction(hash) .flatMap { case Some(block) if !block.mainChain => logger.debug(s"Updating block ${block.hash} which should be on the mainChain") diff --git a/app/src/main/scala/org/alephium/explorer/web/BlockServer.scala b/app/src/main/scala/org/alephium/explorer/web/BlockServer.scala index 22af22a37..1259f9bd8 100644 --- a/app/src/main/scala/org/alephium/explorer/web/BlockServer.scala +++ b/app/src/main/scala/org/alephium/explorer/web/BlockServer.scala @@ -39,7 +39,7 @@ class BlockServer(implicit route(listBlocks.serverLogicSuccess[Future](BlockService.listBlocks(_))), route(getBlockByHash.serverLogic[Future] { hash => BlockService - .getLiteBlockByHash(hash) + .getBlockByHash(hash) .map(_.toRight(ApiError.NotFound(hash.value.toHexString))) }), route(getBlockTransactions.serverLogicSuccess[Future] { case (hash, pagination) => diff --git a/app/src/test/scala/org/alephium/explorer/BlockModelConversionSpec.scala b/app/src/test/scala/org/alephium/explorer/BlockModelConversionSpec.scala new file mode 100644 index 000000000..fc5952f40 --- /dev/null +++ b/app/src/test/scala/org/alephium/explorer/BlockModelConversionSpec.scala @@ -0,0 +1,60 @@ +// Copyright 2018 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see . + +package org.alephium.explorer + +import scala.collection.immutable.ArraySeq + +import org.alephium.explorer.AlephiumSpec +import org.alephium.explorer.ConfigDefaults.groupSetting +import org.alephium.explorer.GenCoreApi._ +import org.alephium.explorer.api.model._ +import org.alephium.explorer.persistence.model._ +import org.alephium.explorer.service.BlockFlowClient + +class BlockModelConversionSpec() extends AlephiumSpec { + + "BlockEntry" should { + "be converted to and from core api BlockEntry" in new Fixture { + forAll(blockEntryProtocolGen) { protocolBlockEntry => + val blockEntity = BlockFlowClient.blockProtocolToEntity(protocolBlockEntry) + + blockEntityToProtocol(blockEntity) is protocolBlockEntry + } + } + } + + trait Fixture { + + def blockEntityToProtocol(blockEntity: BlockEntity): org.alephium.api.model.BlockEntry = { + + val transactions = transactionsApiFromBlockEntity(blockEntity) + + blockEntity.toBlockHeader(groupSetting.groupNum).toApi().toProtocol(transactions) + } + + def transactionsApiFromBlockEntity( + block: BlockEntity + ): ArraySeq[Transaction] = { + block.transactions.map { tx => + tx.toApi( + block.inputs.filter(_.txHash == tx.hash).sortBy(_.inputOrder).map(_.toApi()), + block.outputs.filter(_.txHash == tx.hash).sortBy(_.outputOrder).map(_.toApi()) + ) + } + } + } +} diff --git a/app/src/test/scala/org/alephium/explorer/ExplorerSpec.scala b/app/src/test/scala/org/alephium/explorer/ExplorerSpec.scala index 898c1c2b9..b2e98273b 100644 --- a/app/src/test/scala/org/alephium/explorer/ExplorerSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/ExplorerSpec.scala @@ -79,11 +79,27 @@ trait ExplorerSpec val blockflow: ArraySeq[ArraySeq[model.BlockEntry]] = blockFlowGen(maxChainSize = 5, startTimestamp = TimeStamp.now()).sample.get + val uncles = blockflow + .map(_.flatMap { block => + block.ghostUncles.map { uncle => + blockEntryProtocolGen.sample.get.copy( + hash = uncle.blockHash, + timestamp = block.timestamp, + chainFrom = block.chainFrom, + chainTo = block.chainTo + ) + + } + }) + .flatten + val blocksProtocol: ArraySeq[model.BlockEntry] = blockflow.flatten val blockEntities: ArraySeq[BlockEntity] = blocksProtocol.map(BlockFlowClient.blockProtocolToEntity) - val blocks: ArraySeq[BlockEntry] = blockEntitiesToBlockEntries(ArraySeq(blockEntities)).flatten + val blocks: ArraySeq[BlockEntryTest] = blockEntitiesToBlockEntries( + ArraySeq(blockEntities) + ).flatten val transactions: ArraySeq[Transaction] = blocks.flatMap(_.transactions) @@ -95,7 +111,7 @@ trait ExplorerSpec val blockFlowPort = SocketUtil.temporaryLocalPort(SocketUtil.Both) val blockFlowMock = - new ExplorerSpec.BlockFlowServerMock(localhost, blockFlowPort, blockflow, networkId) + new ExplorerSpec.BlockFlowServerMock(localhost, blockFlowPort, blockflow, uncles, networkId) val coingeckoPort = SocketUtil.temporaryLocalPort(SocketUtil.Both) val coingeckoUri = s"http://${localhost.getHostAddress()}:$coingeckoPort" @@ -382,6 +398,15 @@ trait ExplorerSpec } } + "insert uncle blocks" in { + uncles.foreach { uncle => + Get(s"/blocks/${uncle.hash.toHexString}") check { response => + val res = response.as[BlockEntry] + res.mainChain is false + } + } + } + "generate the documentation" in { Get("/docs") check { response => response.code is StatusCode.Ok @@ -425,6 +450,7 @@ object ExplorerSpec { address: InetAddress, port: Int, blockflow: ArraySeq[ArraySeq[model.BlockEntry]], + uncles: ArraySeq[model.BlockEntry], networkId: NetworkId )(implicit groupSetting: GroupSetting) extends ApiModelCodec @@ -436,6 +462,7 @@ object ExplorerSpec { implicit val groupConfig: GroupConfig = groupSetting.groupConfig val blocks = blockflow.flatten + val blocksWithUncles = blockflow.flatten ++ uncles val cliqueId = CliqueId.generate @@ -482,7 +509,7 @@ object ExplorerSpec { .in(path[BlockHash]) .out(jsonBody[model.BlockEntry]) .serverLogicSuccess[Future] { hash => - Future.successful(blocks.find(_.hash === hash).get) + Future.successful(blocksWithUncles.find(_.hash === hash).get) } ), route( @@ -495,7 +522,7 @@ object ExplorerSpec { Future .successful( model.BlockAndEvents( - blocks.find(_.hash === hash).get, + blocksWithUncles.find(_.hash === hash).get, AVector.from(Gen.listOfN(3, contractEventByBlockHash).sample.get) ) ) diff --git a/app/src/test/scala/org/alephium/explorer/GenApiModel.scala b/app/src/test/scala/org/alephium/explorer/GenApiModel.scala index 9ed6644e7..42c1bf64d 100644 --- a/app/src/test/scala/org/alephium/explorer/GenApiModel.scala +++ b/app/src/test/scala/org/alephium/explorer/GenApiModel.scala @@ -69,12 +69,14 @@ object GenApiModel extends ImplicitConversions { val unlockScriptGen: Gen[ByteString] = hashGen.map(_.bytes) val inputGen: Gen[Input] = for { - outputRef <- outputRefGen - unlockScript <- Gen.option(unlockScriptGen) - txHashRef <- Gen.option(transactionHashGen) - address <- Gen.option(addressGen) - amount <- Gen.option(amountGen) - } yield Input(outputRef, unlockScript, txHashRef, address, amount) + outputRef <- outputRefGen + unlockScript <- Gen.option(unlockScriptGen) + txHashRef <- Gen.option(transactionHashGen) + address <- Gen.option(addressGen) + amount <- Gen.option(amountGen) + tokens <- Gen.option(tokensGen) + contractInput <- arbitrary[Boolean] + } yield Input(outputRef, unlockScript, txHashRef, address, amount, tokens, contractInput) val tokenGen: Gen[Token] = for { id <- tokenIdGen @@ -92,8 +94,9 @@ object GenApiModel extends ImplicitConversions { spent <- Gen.option(transactionHashGen) message <- Gen.option(bytesGen) hint = 0 - key <- outputRefKeyGen.map(_.value) - } yield AssetOutput(hint, key, amount, address, tokens, lockTime, message, spent) + key <- outputRefKeyGen.map(_.value) + fixedOutput <- arbitrary[Boolean] + } yield AssetOutput(hint, key, amount, address, tokens, lockTime, message, spent, fixedOutput) val contractOutputGen: Gen[ContractOutput] = for { @@ -103,7 +106,7 @@ object GenApiModel extends ImplicitConversions { spent <- Gen.option(transactionHashGen) hint = 0 key <- outputRefKeyGen.map(_.value) - } yield ContractOutput(hint, key, amount, address, tokens, spent) + } yield ContractOutput(hint, key, amount, address, tokens, spent, fixedOutput = false) val outputGen: Gen[Output] = Gen.oneOf(assetOutputGen: Gen[Output], contractOutputGen: Gen[Output]) @@ -114,9 +117,14 @@ object GenApiModel extends ImplicitConversions { blockHash <- blockHashGen timestamp <- timestampGen outputs <- Gen.listOfN(5, outputGen) + version <- arbitrary[Byte] + networkId <- arbitrary[Byte] + scriptOpt <- Gen.option(hashGen.map(_.toHexString)) gasAmount <- Gen.posNum[Int] gasPrice <- u256Gen scriptExecutionOk <- arbitrary[Boolean] + inputSignatures <- Gen.listOfN(2, bytesGen) + scriptSignatures <- Gen.listOfN(2, bytesGen) coinbase <- arbitrary[Boolean] } yield Transaction( hash, @@ -124,9 +132,14 @@ object GenApiModel extends ImplicitConversions { timestamp, ArraySeq.empty, ArraySeq.from(outputs), + version, + networkId, + scriptOpt, gasAmount, gasPrice, scriptExecutionOk, + inputSignatures, + scriptSignatures, coinbase ) @@ -135,8 +148,13 @@ object GenApiModel extends ImplicitConversions { hash <- transactionHashGen chainFrom <- groupIndexGen chainTo <- groupIndexGen - inputs <- Gen.listOfN(3, inputGen.map(_.copy(attoAlphAmount = None, txHashRef = None))) - outputs <- Gen.listOfN(3, assetOutputGen.map(_.copy(spent = None))) + inputs <- Gen.listOfN( + 3, + inputGen.map( + _.copy(attoAlphAmount = None, txHashRef = None, tokens = None, contractInput = false) + ) + ) + outputs <- Gen.listOfN(3, assetOutputGen.map(_.copy(spent = None, fixedOutput = true))) gasAmount <- Gen.posNum[Int] gasPrice <- u256Gen lastSeen <- timestampGen @@ -185,15 +203,23 @@ object GenApiModel extends ImplicitConversions { def blockEntryGen(implicit groupSetting: GroupSetting): Gen[BlockEntry] = for { - hash <- blockHashGen - timestamp <- timestampGen - chainFrom <- groupIndexGen - chainTo <- groupIndexGen - height <- heightGen - deps <- Gen.listOfN(2 * groupSetting.groupNum - 1, blockHashGen) - transactions <- Gen.listOfN(2, transactionGen) - mainChain <- arbitrary[Boolean] - hashrate <- arbitrary[Long].map(BigInteger.valueOf) + hash <- blockHashGen + timestamp <- timestampGen + chainFrom <- groupIndexGen + chainTo <- groupIndexGen + height <- heightGen + deps <- Gen.listOfN(2 * groupSetting.groupNum - 1, blockHashGen) + nonce <- bytesGen + depStateHash <- hashGen + txsHash <- hashGen + txsCount <- Gen.posNum[Int] + target <- bytesGen + version <- arbitrary[Byte] + mainChain <- arbitrary[Boolean] + hashrate <- arbitrary[Long].map(BigInteger.valueOf) + parent <- Gen.option(blockHashGen) + ghostUnclesSize <- Gen.choose(0, 5) + ghostUncles <- Gen.listOfN(ghostUnclesSize, ghostUncleGen()) } yield { BlockEntry( hash, @@ -202,9 +228,16 @@ object GenApiModel extends ImplicitConversions { chainTo, height, deps, - transactions, + nonce, + version, + depStateHash, + txsHash, + txsCount, + target, + hashrate, + parent, mainChain, - hashrate + ghostUncles ) } @@ -339,6 +372,11 @@ object GenApiModel extends ImplicitConversions { ) ) + def ghostUncleGen()(implicit groupSetting: GroupSetting): Gen[GhostUncle] = for { + blockHash <- blockHashGen + miner <- addressAssetProtocolGen() + } yield GhostUncle(blockHash, miner) + /** Generates [[Pagination]] instance for the generated data. * * @return diff --git a/app/src/test/scala/org/alephium/explorer/GenCoreApi.scala b/app/src/test/scala/org/alephium/explorer/GenCoreApi.scala index 1da3b3184..16da49d43 100644 --- a/app/src/test/scala/org/alephium/explorer/GenCoreApi.scala +++ b/app/src/test/scala/org/alephium/explorer/GenCoreApi.scala @@ -33,7 +33,7 @@ import org.alephium.explorer.Generators._ import org.alephium.explorer.api.model.{Height, StdInterfaceId} import org.alephium.explorer.persistence.model.ContractEntity import org.alephium.explorer.service.BlockFlowClient -import org.alephium.protocol.model.{BlockHash, ChainIndex, CliqueId, NetworkId, Target} +import org.alephium.protocol.model.{BlockHash, ChainIndex, CliqueId, Hint, NetworkId, Target} import org.alephium.serde._ import org.alephium.util.{AVector, Duration, Hex, I256, TimeStamp, U256} @@ -123,12 +123,12 @@ object GenCoreApi { for { unsigned <- unsignedTxGen scriptExecutionOk <- arbitrary[Boolean] - contractInputsSize <- Gen.choose(0, 5) + contractInputsSize <- Gen.choose(0, 1) contractInputs <- Gen.listOfN(contractInputsSize, outputRefProtocolGen) - generatedOutputsSize <- Gen.choose(0, 5) + generatedOutputsSize <- Gen.choose(0, 1) generatedOutputs <- Gen.listOfN(generatedOutputsSize, outputProtocolGen) - inputSignatures <- Gen.listOfN(2, bytesGen) - scriptSignatures <- Gen.listOfN(2, bytesGen) + inputSignatures <- Gen.listOfN(1, bytesGen) + scriptSignatures <- Gen.listOfN(1, bytesGen) } yield Transaction( unsigned, scriptExecutionOk, @@ -146,13 +146,13 @@ object GenCoreApi { chainTo <- GenApiModel.groupIndexGen height <- GenApiModel.heightGen deps <- Gen.listOfN(2 * groupSetting.groupNum - 1, blockHashGen) - transactionSize <- Gen.choose(1, 10) + transactionSize <- Gen.choose(1, 1) transactions <- Gen.listOfN(transactionSize, transactionProtocolGen) nonce <- bytesGen version <- Gen.posNum[Byte] depStateHash <- hashGen txsHash <- hashGen - ghostUnclesSize <- Gen.choose(0, 5) + ghostUnclesSize <- Gen.choose(0, 1) ghostUncles <- Gen.listOfN(ghostUnclesSize, ghostUncleBlockEntry) } yield { // From `alephium` repo @@ -226,13 +226,12 @@ object GenCoreApi { def fixedOutputAssetProtocolGen(implicit groupSetting: GroupSetting): Gen[FixedAssetOutput] = for { - hint <- Gen.posNum[Int] key <- hashGen amount <- amountGen lockTime <- timestampGen address <- addressAssetProtocolGen() } yield FixedAssetOutput( - hint, + Hint.ofAsset(address.lockupScript.scriptHint).value, key, Amount(amount), address, @@ -251,12 +250,17 @@ object GenCoreApi { def outputContractProtocolGen(implicit groupSetting: GroupSetting): Gen[ContractOutput] = for { - hint <- Gen.posNum[Int] key <- hashGen amount <- amountGen address <- addressContractProtocolGen tokens <- Gen.listOfN(1, tokenProtocolGen) - } yield ContractOutput(hint, key, Amount(amount), address, AVector.from(tokens)) + } yield ContractOutput( + Hint.ofAsset(address.lockupScript.scriptHint).value, + key, + Amount(amount), + address, + AVector.from(tokens) + ) def outputProtocolGen(implicit groupSetting: GroupSetting): Gen[Output] = Gen.oneOf(outputAssetProtocolGen: Gen[Output], outputContractProtocolGen: Gen[Output]) diff --git a/app/src/test/scala/org/alephium/explorer/GenDBModel.scala b/app/src/test/scala/org/alephium/explorer/GenDBModel.scala index c06fcf24f..287ea1ba5 100644 --- a/app/src/test/scala/org/alephium/explorer/GenDBModel.scala +++ b/app/src/test/scala/org/alephium/explorer/GenDBModel.scala @@ -24,6 +24,7 @@ import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary import org.alephium.api.model.{Val, ValAddress, ValByteVec} +import org.alephium.explorer.ConfigDefaults.groupSetting import org.alephium.explorer.GenApiModel._ import org.alephium.explorer.GenCoreApi.{blockEntryProtocolGen, valByteVecGen, valGen} import org.alephium.explorer.GenCoreProtocol._ @@ -60,6 +61,7 @@ object GenDBModel { outputOrder <- arbitrary[Int] txOrder <- arbitrary[Int] coinbase <- arbitrary[Boolean] + fixedOutput <- arbitrary[Boolean] } yield OutputEntity( blockHash = blockHash, txHash = txHash, @@ -77,7 +79,8 @@ object GenDBModel { txOrder = txOrder, spentFinalized = None, spentTimestamp = None, - coinbase = coinbase + coinbase = coinbase, + fixedOutput = fixedOutput ) val finalizedOutputEntityGen: Gen[OutputEntity] = @@ -118,9 +121,10 @@ object GenDBModel { @SuppressWarnings(Array("org.wartremover.warts.DefaultArguments")) def inputEntityGen(outputEntityGen: Gen[OutputEntity] = outputEntityGen): Gen[InputEntity] = for { - outputEntity <- outputEntityGen - unlockScript <- Gen.option(unlockScriptGen) - txOrder <- arbitrary[Int] + outputEntity <- outputEntityGen + unlockScript <- Gen.option(unlockScriptGen) + txOrder <- arbitrary[Int] + contractInput <- arbitrary[Boolean] } yield { InputEntity( blockHash = outputEntity.blockHash, @@ -135,7 +139,8 @@ object GenDBModel { None, None, None, - None + None, + contractInput ) } @@ -352,6 +357,9 @@ object GenDBModel { timestamp <- timestampGen chainFrom <- groupIndexGen chainTo <- groupIndexGen + version <- arbitrary[Byte] + networkId <- arbitrary[Byte] + scriptOpt <- Gen.option(hashGen.map(_.toHexString)) gasAmount <- Gen.posNum[Int] gasPrice <- u256Gen order <- Gen.posNum[Int] @@ -364,6 +372,9 @@ object GenDBModel { timestamp = timestamp, chainFrom = chainFrom, chainTo = chainTo, + version = version, + networkId = networkId, + scriptOpt = scriptOpt, gasAmount = gasAmount, gasPrice = gasPrice, order = order, @@ -414,6 +425,7 @@ object GenDBModel { target <- bytesGen hashrate <- arbitrary[Long].map(BigInteger.valueOf) mainChain <- Arbitrary.arbitrary[Boolean] + deps <- Gen.listOfN(2 * groupSetting.groupNum - 1, blockHashGen) parent <- Gen.option(blockHashGen) } yield BlockHeader( hash = hash, @@ -429,7 +441,9 @@ object GenDBModel { txsCount = txsCount, target = target, hashrate = hashrate, - parent = parent + parent = parent, + deps = deps, + ghostUncles = None ) val blockHeaderTransactionEntityGen: Gen[(BlockHeader, List[TransactionEntity])] = @@ -438,19 +452,24 @@ object GenDBModel { transaction <- Gen.listOf(transactionEntityGen(Gen.const(blockHeader.hash))) } yield (blockHeader, transaction) - def blockHeaderWithHashrate(timestamp: TimeStamp, hashrate: Double): Gen[BlockHeader] = { + def blockHeaderWithHashrate(timestamp: TimeStamp, hashrate: Double)(implicit + groupSetting: GroupSetting + ): Gen[BlockHeader] = { for { - hash <- blockHashGen - from <- groupIndexGen - to <- groupIndexGen - height <- heightGen - nonce <- bytesGen - version <- Gen.posNum[Byte] - depStateHash <- hashGen - txsHash <- hashGen - txsCount <- Gen.posNum[Int] - target <- bytesGen - parent <- Gen.option(blockHashGen) + hash <- blockHashGen + from <- groupIndexGen + to <- groupIndexGen + height <- heightGen + nonce <- bytesGen + version <- Gen.posNum[Byte] + depStateHash <- hashGen + txsHash <- hashGen + txsCount <- Gen.posNum[Int] + target <- bytesGen + parent <- Gen.option(blockHashGen) + deps <- Gen.listOfN(2 * groupSetting.groupNum - 1, blockHashGen) + ghostUnclesSize <- Gen.choose(0, 5) + ghostUncles <- Gen.option(Gen.listOfN(ghostUnclesSize, ghostUncleGen())) } yield { BlockHeader( hash, @@ -466,40 +485,13 @@ object GenDBModel { txsCount, target, BigDecimal(hashrate).toBigInt.bigInteger, - parent + parent, + deps, + ghostUncles ) } } - /** Update toUpdate's primary key to be the same as `original` */ - def copyPrimaryKeys(original: BlockDepEntity, toUpdate: BlockDepEntity): BlockDepEntity = - toUpdate.copy( - hash = original.hash, - dep = original.dep - ) - - val blockDepGen: Gen[BlockDepEntity] = - for { - hash <- blockHashGen - dep <- blockHashGen - order <- Gen.posNum[Int] - } yield BlockDepEntity( - hash = hash, - dep = dep, - order = order - ) - - /** Generates a tuple2 of [[BlockDepEntity]] where the second one has the same primary key as the - * first one but with different values - */ - val blockDepUpdatedGen: Gen[(BlockDepEntity, BlockDepEntity)] = - for { - dep1 <- blockDepGen - dep2 <- blockDepGen - } yield { - (dep1, copyPrimaryKeys(dep1, dep2)) - } - /** Table `uinputs` applies uniqueness on `(output_ref_key, tx_hash)`. * * Table `uoutputs` applies uniqueness on `(tx_hash, address, uoutput_order)`. diff --git a/app/src/test/scala/org/alephium/explorer/Generators.scala b/app/src/test/scala/org/alephium/explorer/Generators.scala index fe9a36d6a..665c1c554 100644 --- a/app/src/test/scala/org/alephium/explorer/Generators.scala +++ b/app/src/test/scala/org/alephium/explorer/Generators.scala @@ -16,9 +16,8 @@ package org.alephium.explorer -import java.math.BigInteger - import scala.collection.immutable.ArraySeq +import scala.util.Random import org.scalacheck.Gen @@ -33,41 +32,58 @@ object Generators { def parentIndex(chainTo: GroupIndex)(implicit groupSetting: GroupSetting) = groupSetting.groupNum - 1 + chainTo.value + def blockEntityToTransactions( + block: BlockEntity, + outputs: ArraySeq[OutputEntity] + ): ArraySeq[Transaction] = { + val coinbaseTxId = block.transactions.last.hash + block.transactions.map { tx => + Transaction( + tx.hash, + block.hash, + block.timestamp, + block.inputs + .filter(_.txHash === tx.hash) + .map(input => + inputEntityToApi(input, outputs(Random.nextInt(outputs.size))) + ), // TODO Fix when we have a valid blockchain generator + block.outputs.filter(_.txHash === tx.hash).map(out => outputEntityToApi(out, None)), + tx.version, + tx.networkId, + tx.scriptOpt, + tx.gasAmount, + tx.gasPrice, + tx.scriptExecutionOk, + tx.inputSignatures.getOrElse(ArraySeq.empty), + tx.scriptSignatures.getOrElse(ArraySeq.empty), + coinbase = coinbaseTxId == tx.hash + ) + } + } + def blockEntitiesToBlockEntries( blocks: ArraySeq[ArraySeq[BlockEntity]] - ): ArraySeq[ArraySeq[BlockEntry]] = { - val outputs: ArraySeq[OutputEntity] = blocks.flatMap(_.flatMap(_.outputs)) - + ): ArraySeq[ArraySeq[BlockEntryTest]] = { blocks.map(_.map { block => - val coinbaseTxId = block.transactions.last.hash - val transactions = - block.transactions.map { tx => - Transaction( - tx.hash, - block.hash, - block.timestamp, - block.inputs - .filter(_.txHash === tx.hash) - .map(input => - inputEntityToApi(input, outputs.head) - ), // TODO Fix when we have a valid blockchain generator - block.outputs.filter(_.txHash === tx.hash).map(out => outputEntityToApi(out, None)), - tx.gasAmount, - tx.gasPrice, - tx.scriptExecutionOk, - coinbase = coinbaseTxId == tx.hash - ) - } - BlockEntry( + val transactions = blockEntityToTransactions(block, blocks.flatMap(_.flatMap(_.outputs))) + BlockEntryTest( block.hash, block.timestamp, block.chainFrom, block.chainTo, block.height, - block.deps, transactions, + block.deps, + block.nonce, + block.version, + block.depStateHash, + block.txsHash, + transactions.size, + block.target, + block.hashrate, + None, mainChain = true, - BigInteger.ZERO + ghostUncles = block.ghostUncles ) }) } @@ -79,15 +95,26 @@ object Generators { Some(outputRef.txHash), Some(outputRef.address), Some(outputRef.amount), - outputRef.tokens + outputRef.tokens, + input.contractInput ) def outputEntityToApi(o: OutputEntity, spent: Option[TransactionId]): Output = { o.outputType match { case OutputEntity.Asset => - AssetOutput(o.hint, o.key, o.amount, o.address, o.tokens, o.lockTime, o.message, spent) + AssetOutput( + o.hint, + o.key, + o.amount, + o.address, + o.tokens, + o.lockTime, + o.message, + spent, + o.fixedOutput + ) case OutputEntity.Contract => - ContractOutput(o.hint, o.key, o.amount, o.address, o.tokens, spent) + ContractOutput(o.hint, o.key, o.amount, o.address, o.tokens, spent, o.fixedOutput) } } } diff --git a/app/src/test/scala/org/alephium/explorer/api/model/ApiModelSpec.scala b/app/src/test/scala/org/alephium/explorer/api/model/ApiModelSpec.scala index 936e8229e..9494ac84b 100644 --- a/app/src/test/scala/org/alephium/explorer/api/model/ApiModelSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/api/model/ApiModelSpec.scala @@ -54,9 +54,14 @@ class ApiModelSpec() extends AlephiumSpec { | "timestamp": ${tx.timestamp.millis}, | "inputs": [], | "outputs": ${write(tx.outputs)}, + | "version": ${tx.version}, + | "networkId": ${tx.networkId}, + | "scriptOpt": ${tx.scriptOpt.map(s => s""""$s"""").getOrElse("null")}, | "gasAmount": ${tx.gasAmount}, | "gasPrice": "${tx.gasPrice}", | "scriptExecutionOk": ${tx.scriptExecutionOk}, + | "inputSignatures": ${write(tx.inputSignatures)}, + | "scriptSignatures": ${write(tx.scriptSignatures)}, | "coinbase": ${tx.coinbase} |}""".stripMargin check(tx, expected) @@ -87,7 +92,8 @@ class ApiModelSpec() extends AlephiumSpec { unlockScript = None, address = Some(address), attoAlphAmount = Some(ALPH.alph(10)), - tokens = None + tokens = None, + contractInput = false ) ), ArraySeq( @@ -99,7 +105,8 @@ class ApiModelSpec() extends AlephiumSpec { tokens = None, lockTime = None, message = None, - spent = None + spent = None, + fixedOutput = true ), AssetOutput( hint = 0, @@ -109,7 +116,8 @@ class ApiModelSpec() extends AlephiumSpec { tokens = None, lockTime = None, message = None, - spent = None + spent = None, + fixedOutput = true ), AssetOutput( hint = 0, @@ -119,12 +127,18 @@ class ApiModelSpec() extends AlephiumSpec { tokens = None, lockTime = None, message = None, - spent = None + spent = None, + fixedOutput = true ) ), + version = 0, + networkId = 0, + scriptOpt = None, gasAmount = 1, gasPrice = ALPH.alph(1), false, + ArraySeq.empty, + ArraySeq.empty, true ) @@ -145,9 +159,14 @@ class ApiModelSpec() extends AlephiumSpec { | "timestamp": ${tx.timestamp.millis}, | "inputs": ${write(tx.inputs)}, | "outputs": ${write(tx.outputs)}, + | "version": ${tx.version}, + | "networkId": ${tx.networkId}, + | "scriptOpt": ${tx.scriptOpt.map(s => s""""$s"""").getOrElse("null")}, | "gasAmount": ${tx.gasAmount}, | "gasPrice": "${tx.gasPrice}", | "scriptExecutionOk": ${tx.scriptExecutionOk}, + | "inputSignatures": ${write(tx.inputSignatures)}, + | "scriptSignatures": ${write(tx.scriptSignatures)}, | "coinbase": ${tx.coinbase} |}""".stripMargin check(AcceptedTransaction.from(tx), expected) @@ -196,6 +215,7 @@ class ApiModelSpec() extends AlephiumSpec { | ${output.spent .map(spent => s""","spent": "${spent.value.toHexString}"""") .getOrElse("")} + | ,"fixedOutput": ${output.fixedOutput} |}""".stripMargin check(output, expected) } @@ -215,6 +235,7 @@ class ApiModelSpec() extends AlephiumSpec { | ${output.spent .map(spent => s""","spent": "${spent.value.toHexString}"""") .getOrElse("")} + | ,"fixedOutput": ${output.fixedOutput} |}""".stripMargin check(output, expected) } @@ -236,6 +257,10 @@ class ApiModelSpec() extends AlephiumSpec { | ${input.attoAlphAmount .map(attoAlphAmount => s""","attoAlphAmount": "${attoAlphAmount}"""") .getOrElse("")} + | ${input.tokens + .map(tokens => s""","tokens": ${write(tokens)}""") + .getOrElse("")} + | ,"contractInput": ${input.contractInput} |}""".stripMargin check(input, expected) } @@ -302,9 +327,18 @@ class ApiModelSpec() extends AlephiumSpec { | "chainTo": ${block.chainTo.value}, | "height": ${block.height.value}, | "deps": ${write(block.deps)}, - | "transactions": ${write(block.transactions)}, + | "nonce": ${write(block.nonce)}, + | "version": ${block.version}, + | "depStateHash": "${block.depStateHash.toHexString}", + | "txsHash": "${block.txsHash.toHexString}", + | "txNumber": ${block.txNumber}, + | "target": ${write(block.target)}, + | "hashRate": ${write(block.hashRate)}, + | "parent": ${if (block.parent.isDefined) { + s""""${block.parent.get.toHexString}"""" + } else { "null" }}, | "mainChain": ${block.mainChain}, - | "hashRate": "${block.hashRate}" + | "ghostUncles": ${write(block.ghostUncles)} |}""".stripMargin check(block, expected) } diff --git a/app/src/test/scala/org/alephium/explorer/api/model/BlockEntryTest.scala b/app/src/test/scala/org/alephium/explorer/api/model/BlockEntryTest.scala new file mode 100644 index 000000000..08da07465 --- /dev/null +++ b/app/src/test/scala/org/alephium/explorer/api/model/BlockEntryTest.scala @@ -0,0 +1,48 @@ +// Copyright 2018 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see . + +package org.alephium.explorer.api.model + +import java.math.BigInteger + +import scala.collection.immutable.ArraySeq + +import akka.util.ByteString + +import org.alephium.explorer.service.FlowEntity +import org.alephium.protocol.Hash +import org.alephium.protocol.model.{BlockHash, GroupIndex} +import org.alephium.util.TimeStamp + +final case class BlockEntryTest( + hash: BlockHash, + timestamp: TimeStamp, + chainFrom: GroupIndex, + chainTo: GroupIndex, + height: Height, + transactions: ArraySeq[Transaction], + deps: ArraySeq[BlockHash], + nonce: ByteString, + version: Byte, + depStateHash: Hash, + txsHash: Hash, + txsCount: Int, + target: ByteString, + hashRate: BigInteger, + parent: Option[BlockHash], + mainChain: Boolean, + ghostUncles: ArraySeq[GhostUncle] +) extends FlowEntity diff --git a/app/src/test/scala/org/alephium/explorer/persistence/dao/BlockDaoSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/dao/BlockDaoSpec.scala index ee93a2132..653ed3230 100644 --- a/app/src/test/scala/org/alephium/explorer/persistence/dao/BlockDaoSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/persistence/dao/BlockDaoSpec.scala @@ -88,12 +88,10 @@ class BlockDaoSpec extends AlephiumFutureSpec with DatabaseFixtureForEach with D val inputQuery = InputSchema.table.filter(_.blockHash === block.hash).result val outputQuery = OutputSchema.table.filter(_.blockHash === block.hash).result - val blockDepsQuery = - BlockDepsSchema.table.filter(_.hash === block.hash).map(_.dep).result val transactionsQuery = TransactionSchema.table.filter(_.blockHash === block.hash).result - val queries = Seq(inputQuery, outputQuery, blockDepsQuery, transactionsQuery) - val dbInputs = Seq(block.inputs, block.outputs, block.deps, block.transactions) + val queries = Seq(inputQuery, outputQuery, transactionsQuery) + val dbInputs = Seq(block.inputs, block.outputs, block.transactions) def checkDuplicates[T](dbInput: Seq[T], dbOutput: Seq[T]) = { dbOutput.size is dbInput.size diff --git a/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockDepQueriesSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockDepQueriesSpec.scala deleted file mode 100644 index 70a417379..000000000 --- a/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockDepQueriesSpec.scala +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The Alephium Authors -// This file is part of the alephium project. -// -// The library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the library. If not, see . - -package org.alephium.explorer.persistence.queries - -import org.scalacheck.Gen -import slick.jdbc.PostgresProfile.api._ - -import org.alephium.explorer.{AlephiumFutureSpec, GenDBModel} -import org.alephium.explorer.persistence.{DatabaseFixtureForEach, DBRunner} -import org.alephium.explorer.persistence.queries.BlockDepQueries._ -import org.alephium.explorer.persistence.schema.BlockDepsSchema - -class BlockDepQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach with DBRunner { - - "insert and ignore block_deps" in { - - forAll(Gen.listOf(GenDBModel.blockDepUpdatedGen)) { deps => - // clean existing rows - run(BlockDepsSchema.table.delete).futureValue - - val original = deps.map(_._1) - val ignored = deps.map(_._2) - - run(insertBlockDeps(original)).futureValue is original.size - run(BlockDepsSchema.table.result).futureValue is original - - // Ignore the same data with do nothing order - run(insertBlockDeps(ignored)).futureValue is 0 - // it should contain original rows - run(BlockDepsSchema.table.result).futureValue should contain allElementsOf original - } - } - -} diff --git a/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockQueriesSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockQueriesSpec.scala index dd5ec96e2..2e0c44bd4 100644 --- a/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockQueriesSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/persistence/queries/BlockQueriesSpec.scala @@ -122,7 +122,6 @@ class BlockQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach wi run(TransactionSchema.table.delete).futureValue run(InputSchema.table.delete).futureValue run(OutputSchema.table.delete).futureValue - run(BlockDepsSchema.table.delete).futureValue // execute insert on blocks and expect all tables get inserted run(BlockQueries.insertBlockEntity(entities, groupSetting.groupNum)).futureValue is () @@ -147,11 +146,6 @@ class BlockQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach wi // val expectedOutputs = entities.flatMap(_.outputs) // actualOutputs should contain allElementsOf expectedOutputs - // check block_deps table - val actualDeps = run(BlockDepsSchema.table.result).futureValue - val expectedBlockDeps = entities.flatMap(_.toBlockDepEntities()) - actualDeps should contain allElementsOf expectedBlockDeps - // There is no need for testing updates here since updates are already // tested each table's individual test-cases. } diff --git a/app/src/test/scala/org/alephium/explorer/persistence/queries/InputQueriesSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/queries/InputQueriesSpec.scala index 99bc0f105..3dda46bb9 100644 --- a/app/src/test/scala/org/alephium/explorer/persistence/queries/InputQueriesSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/persistence/queries/InputQueriesSpec.scala @@ -106,10 +106,11 @@ class InputQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach wi hint = entity.hint, outputRefKey = entity.outputRefKey, unlockScript = entity.unlockScript, - txHashRef = entity.outputRefTxHash, - address = entity.outputRefAddress, - amount = entity.outputRefAmount, - token = entity.outputRefTokens + outputRefTxHash = entity.outputRefTxHash, + outputRefAddress = entity.outputRefAddress, + outputRefAmount = entity.outputRefAmount, + outputRefTokens = entity.outputRefTokens, + contractInput = entity.contractInput ) } @@ -155,7 +156,8 @@ class InputQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach wi outputRefTxHash = input.outputRefTxHash, outputRefAddress = input.outputRefAddress, outputRefAmount = input.outputRefAmount, - outputRefTokens = input.outputRefTokens + outputRefTokens = input.outputRefTokens, + contractInput = input.contractInput ) actual.toList should contain only expected diff --git a/app/src/test/scala/org/alephium/explorer/persistence/queries/OutputQueriesSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/queries/OutputQueriesSpec.scala index 56ee02d97..50d273c36 100644 --- a/app/src/test/scala/org/alephium/explorer/persistence/queries/OutputQueriesSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/persistence/queries/OutputQueriesSpec.scala @@ -101,7 +101,8 @@ class OutputQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach w tokens = entity.tokens, lockTime = entity.lockTime, message = entity.message, - spent = entity.spentFinalized + spentFinalized = entity.spentFinalized, + fixedOutput = entity.fixedOutput ) } @@ -149,7 +150,8 @@ class OutputQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForEach w tokens = output.tokens, lockTime = output.lockTime, message = output.message, - spentFinalized = output.spentFinalized + spentFinalized = output.spentFinalized, + fixedOutput = output.fixedOutput ) actual.toList should contain only expected diff --git a/app/src/test/scala/org/alephium/explorer/persistence/queries/TransactionQueriesSpec.scala b/app/src/test/scala/org/alephium/explorer/persistence/queries/TransactionQueriesSpec.scala index 77f9eeca4..05c0ee8db 100644 --- a/app/src/test/scala/org/alephium/explorer/persistence/queries/TransactionQueriesSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/persistence/queries/TransactionQueriesSpec.scala @@ -200,9 +200,14 @@ class TransactionQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForE output.timestamp, inputs, ArraySeq(outputEntityToApi(output, spent)), + version = 1, + networkId = 1, + scriptOpt = None, 1, ALPH.alph(1), scriptExecutionOk = true, + inputSignatures = ArraySeq.empty, + scriptSignatures = ArraySeq.empty, coinbase = false ) } @@ -233,6 +238,9 @@ class TransactionQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForE tx1.timestamp, chainFrom, chainTo, + tx1.version, + tx1.networkId, + tx1.scriptOpt, tx1.gasAmount, tx1.gasPrice, 0, @@ -575,7 +583,8 @@ class TransactionQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForE 0, false, None, - None + None, + fixedOutput = false ) def input(hint: Int, outputRefKey: Hash): InputEntity = @@ -592,7 +601,8 @@ class TransactionQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForE None, None, None, - None + None, + contractInput = false ) def transaction(output: OutputEntity): TransactionEntity = { TransactionEntity( @@ -601,6 +611,9 @@ class TransactionQueriesSpec extends AlephiumFutureSpec with DatabaseFixtureForE output.timestamp, GroupIndex.Zero, new GroupIndex(1), + version = 1, + networkId = 1, + scriptOpt = None, 1, ALPH.alph(1), 0, diff --git a/app/src/test/scala/org/alephium/explorer/service/BlockFlowSyncServiceSpec.scala b/app/src/test/scala/org/alephium/explorer/service/BlockFlowSyncServiceSpec.scala index 3f80dedd3..5bc20755b 100644 --- a/app/src/test/scala/org/alephium/explorer/service/BlockFlowSyncServiceSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/service/BlockFlowSyncServiceSpec.scala @@ -202,25 +202,35 @@ class BlockFlowSyncServiceSpec extends AlephiumFutureSpec with DatabaseFixtureFo def blockFlowEntity: ArraySeq[ArraySeq[BlockEntity]] = chains :+ chainOToO - def blockFlow: ArraySeq[ArraySeq[BlockEntry]] = + val uncles = blockFlowEntity.map(_.flatMap { block => + block.ghostUncles.map { uncle => + blockEntity(None, ChainIndex(block.chainFrom, block.chainTo)).copy(hash = uncle.blockHash) + } + }) + + def blockFlow: ArraySeq[ArraySeq[BlockEntryTest]] = blockEntitiesToBlockEntries(blockFlowEntity) implicit val blockCache: BlockCache = TestBlockCache() def blockEntities = ArraySeq.from(blockFlowEntity.flatten) - def blocks: ArraySeq[BlockEntry] = blockFlow.flatten + def unclesEntities = ArraySeq.from(uncles.flatten) + + def blocksAndUncles = blockEntities ++ unclesEntities + + def blocks: ArraySeq[BlockEntryTest] = blockFlow.flatten implicit val blockFlowClient: BlockFlowClient = new EmptyBlockFlowClient { override def fetchBlock(from: GroupIndex, hash: BlockHash): Future[BlockEntity] = - Future.successful(blockEntities.find(_.hash === hash).get) + Future.successful(blocksAndUncles.find(_.hash === hash).get) override def fetchBlockAndEvents( fromGroup: GroupIndex, hash: BlockHash ): Future[BlockEntityWithEvents] = Future.successful( - BlockEntityWithEvents(blockEntities.find(_.hash === hash).get, ArraySeq.empty) + BlockEntityWithEvents(blocksAndUncles.find(_.hash === hash).get, ArraySeq.empty) ) override def fetchBlocks( diff --git a/app/src/test/scala/org/alephium/explorer/service/EmptyBlockService.scala b/app/src/test/scala/org/alephium/explorer/service/EmptyBlockService.scala index a758f1156..56d5f4d53 100644 --- a/app/src/test/scala/org/alephium/explorer/service/EmptyBlockService.scala +++ b/app/src/test/scala/org/alephium/explorer/service/EmptyBlockService.scala @@ -29,6 +29,11 @@ import org.alephium.protocol.model.BlockHash trait EmptyBlockService extends BlockService { + def getBlockByHash(hash: BlockHash)(implicit + ec: ExecutionContext, + dc: DatabaseConfig[PostgresProfile] + ): Future[Option[BlockEntry]] = Future.successful(None) + def getLiteBlockByHash(hash: BlockHash)(implicit ec: ExecutionContext, dc: DatabaseConfig[PostgresProfile] diff --git a/app/src/test/scala/org/alephium/explorer/service/HashrateServiceSpec.scala b/app/src/test/scala/org/alephium/explorer/service/HashrateServiceSpec.scala index 96da7e93d..71ecb50cb 100644 --- a/app/src/test/scala/org/alephium/explorer/service/HashrateServiceSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/service/HashrateServiceSpec.scala @@ -23,6 +23,7 @@ import scala.collection.immutable.ArraySeq import slick.jdbc.PostgresProfile.api._ import org.alephium.explorer.AlephiumFutureSpec +import org.alephium.explorer.ConfigDefaults.groupSetting import org.alephium.explorer.GenDBModel.blockHeaderWithHashrate import org.alephium.explorer.api.model.{Hashrate, IntervalType} import org.alephium.explorer.persistence.{DatabaseFixtureForEach, DBRunner} diff --git a/app/src/test/scala/org/alephium/explorer/service/TokenSupplyServiceSpec.scala b/app/src/test/scala/org/alephium/explorer/service/TokenSupplyServiceSpec.scala index 0993bebfe..4b5d12af0 100644 --- a/app/src/test/scala/org/alephium/explorer/service/TokenSupplyServiceSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/service/TokenSupplyServiceSpec.scala @@ -207,7 +207,8 @@ class TokenSupplyServiceSpec extends AlephiumFutureSpec with DatabaseFixtureForE None, None, None, - None + None, + contractInput = false ) }, diff --git a/app/src/test/scala/org/alephium/explorer/service/TransactionServiceSpec.scala b/app/src/test/scala/org/alephium/explorer/service/TransactionServiceSpec.scala index b28e337e3..cbab589f4 100644 --- a/app/src/test/scala/org/alephium/explorer/service/TransactionServiceSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/service/TransactionServiceSpec.scala @@ -103,10 +103,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF val fetchedAmout = BlockDao - .get(block.hash) + .getTransactions(block.hash, Pagination.unsafe(1, 1000)) .futureValue - .get - .transactions .flatMap(_.outputs.map(_.attoAlphAmount)) .head fetchedAmout is amount @@ -128,6 +126,9 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF ts0, groupIndex, groupIndex, + version, + networkId, + scriptOpt, gasAmount, gasPrice, 0, @@ -156,7 +157,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF 0, coinbase = false, None, - None + None, + fixedOutput = true ) val block0 = defaultBlockEntity.copy( @@ -176,6 +178,9 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF ts1, groupIndex, groupIndex, + version, + networkId, + scriptOpt, gasAmount1, gasPrice1, 0, @@ -198,7 +203,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF None, None, None, - None + None, + contractInput = false ) val output1 = OutputEntity( blockHash1, @@ -217,7 +223,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF 0, coinbase = false, None, - None + None, + fixedOutput = true ) val block1 = defaultBlockEntity.copy( @@ -242,11 +249,26 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF ts0, ArraySeq.empty, ArraySeq( - AssetOutput(output0.hint, output0.key, U256.One, address0, None, None, None, Some(tx1.hash)) + AssetOutput( + output0.hint, + output0.key, + U256.One, + address0, + None, + None, + None, + Some(tx1.hash), + true + ) ), + version, + networkId, + scriptOpt, gasAmount, gasPrice, scriptExecutionOk = true, + scriptSignatures = ArraySeq.empty, + inputSignatures = ArraySeq.empty, coinbase = false ) @@ -255,12 +277,27 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF blockHash1, ts1, ArraySeq( - Input(OutputRef(0, output0.key), None, Some(output0.txHash), Some(address0), Some(U256.One)) + Input( + OutputRef(0, output0.key), + None, + Some(output0.txHash), + Some(address0), + Some(U256.One), + None, + false + ) + ), + ArraySeq( + AssetOutput(output1.hint, output1.key, U256.One, address1, None, None, None, None, true) ), - ArraySeq(AssetOutput(output1.hint, output1.key, U256.One, address1, None, None, None, None)), + version, + networkId, + scriptOpt, gasAmount1, gasPrice1, scriptExecutionOk = true, + scriptSignatures = ArraySeq.empty, + inputSignatures = ArraySeq.empty, coinbase = false ) @@ -283,6 +320,9 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF ts0, groupIndex, groupIndex, + version, + networkId, + scriptOpt, Gen.posNum[Int].sample.get, amountGen.sample.get, 0, @@ -311,7 +351,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF 0, coinbase = false, None, - None + None, + fixedOutput = true ) val block0 = defaultBlockEntity.copy( @@ -554,8 +595,11 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF trait Fixture { implicit val blockCache: BlockCache = TestBlockCache() - val groupIndex = GroupIndex.Zero - val chainIndex = ChainIndex(groupIndex, groupIndex) + val groupIndex = GroupIndex.Zero + val chainIndex = ChainIndex(groupIndex, groupIndex) + val version: Byte = 1 + val networkId: Byte = 1 + val scriptOpt = None val defaultBlockEntity: BlockEntity = BlockEntity( @@ -574,7 +618,8 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF depStateHash = hashGen.sample.get, txsHash = hashGen.sample.get, target = bytesGen.sample.get, - hashrate = BigInteger.ZERO + hashrate = BigInteger.ZERO, + ghostUncles = ArraySeq.empty ) } diff --git a/app/src/test/scala/org/alephium/explorer/web/AddressServerSpec.scala b/app/src/test/scala/org/alephium/explorer/web/AddressServerSpec.scala index 5035a534a..4e5f5cb46 100644 --- a/app/src/test/scala/org/alephium/explorer/web/AddressServerSpec.scala +++ b/app/src/test/scala/org/alephium/explorer/web/AddressServerSpec.scala @@ -26,6 +26,7 @@ import akka.util.ByteString import io.reactivex.rxjava3.core.Flowable import io.vertx.core.buffer.Buffer import org.scalacheck.Gen +import org.scalatest.time.{Seconds, Span} import slick.basic.DatabaseConfig import slick.jdbc.PostgresProfile import sttp.model.{Header, StatusCode} @@ -63,6 +64,9 @@ class AddressServerSpec() with DatabaseFixtureForAll with HttpServerFixture { + implicit override val patienceConfig: PatienceConfig = + PatienceConfig(timeout = Span(120, Seconds)) + val exportTxsNumberThreshold = 1000 var addressHasMoreTxs = false @@ -317,7 +321,7 @@ class AddressServerSpec() def getToTs(intervalType: IntervalType) = fromTs + maxTimeSpan(intervalType).millis - "return the deprecated amount history as json" in { + "return the deprecated amount history as json" ignore { intervalTypes.foreach { intervalType => val toTs = getToTs(intervalType) diff --git a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/DataGenerator.scala b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/DataGenerator.scala index ae507343b..e955abe0c 100644 --- a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/DataGenerator.scala +++ b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/DataGenerator.scala @@ -53,6 +53,9 @@ object DataGenerator { timestamp = blockTimestamp, chainFrom = new GroupIndex(1), chainTo = new GroupIndex(3), + version = 1, + networkId = 1, + scriptOpt = Some(Random.alphanumeric.take(10).mkString), gasAmount = Random.nextInt(1000), gasPrice = U256.unsafe(0), order = Random.nextInt(1000), @@ -84,7 +87,8 @@ object DataGenerator { txOrder = order, coinbase = transaction.hash == coinbaseTxHash, spentFinalized = None, - spentTimestamp = None + spentTimestamp = None, + fixedOutput = Random.nextBoolean() ) } } @@ -104,7 +108,8 @@ object DataGenerator { None, None, None, - None + None, + contractInput = Random.nextBoolean() ) } @@ -144,7 +149,8 @@ object DataGenerator { depStateHash = Hash.generate, txsHash = Hash.generate, target = ByteString.fromString(Random.alphanumeric.take(10).mkString), - hashrate = BigInteger.valueOf(Random.nextLong(Long.MaxValue)) + hashrate = BigInteger.valueOf(Random.nextLong(Long.MaxValue)), + ghostUncles = ArraySeq.empty ) } diff --git a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/AddressReadState.scala b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/AddressReadState.scala index d04aedf13..4483bc5a6 100644 --- a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/AddressReadState.scala +++ b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/AddressReadState.scala @@ -99,7 +99,8 @@ class AddressReadState(val db: DBExecutor) None, None, None, - None + None, + contractInput = false ) } private def generateTransaction( @@ -113,6 +114,9 @@ class AddressReadState(val db: DBExecutor) timestamp = timestamp, chainFrom = new GroupIndex(1), chainTo = new GroupIndex(3), + version = 1, + networkId = 1, + scriptOpt = Some(Random.alphanumeric.take(10).mkString), gasAmount = 0, gasPrice = U256.unsafe(0), order = 0, @@ -146,7 +150,8 @@ class AddressReadState(val db: DBExecutor) txOrder = 0, coinbase = false, spentFinalized = None, - spentTimestamp = None + spentTimestamp = None, + fixedOutput = Random.nextBoolean() ) } @@ -188,7 +193,8 @@ class AddressReadState(val db: DBExecutor) depStateHash = Blake2b.generate, txsHash = Blake2b.generate, target = ByteString.emptyByteString, - hashrate = BigInteger.ONE + hashrate = BigInteger.ONE, + ghostUncles = ArraySeq.empty ) }) diff --git a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/BlockHeaderMainChainReadState.scala b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/BlockHeaderMainChainReadState.scala index d26084ef0..54ef47e32 100644 --- a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/BlockHeaderMainChainReadState.scala +++ b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/BlockHeaderMainChainReadState.scala @@ -18,6 +18,7 @@ package org.alephium.explorer.benchmark.db.state import java.math.BigInteger +import scala.collection.immutable.ArraySeq import scala.util.Random import akka.util.ByteString @@ -59,7 +60,9 @@ class BlockHeaderMainChainReadState( txsCount = Random.nextInt(), target = ByteString.emptyByteString, hashrate = BigInteger.ONE, - parent = Some(BlockHash.generate) + parent = Some(BlockHash.generate), + deps = ArraySeq.from(0 to 4).map(_ => BlockHash.generate), + ghostUncles = None ) def persist(data: Array[BlockHeader]): Unit = { diff --git a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/ListBlocksReadState.scala b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/ListBlocksReadState.scala index 942311783..b43a526d4 100644 --- a/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/ListBlocksReadState.scala +++ b/benchmark/src/test/scala/org/alephium/explorer/benchmark/db/state/ListBlocksReadState.scala @@ -18,6 +18,7 @@ package org.alephium.explorer.benchmark.db.state import java.math.BigInteger +import scala.collection.immutable.ArraySeq import scala.util.Random import akka.util.ByteString @@ -80,7 +81,9 @@ class ListBlocksReadState( txsCount = scala.math.abs(Random.nextInt()), target = ByteString.emptyByteString, hashrate = BigInteger.ONE, - parent = Some(BlockHash.generate) + parent = Some(BlockHash.generate), + deps = ArraySeq.from(0 to 4).map(_ => BlockHash.generate), + ghostUncles = None ) private def generateTransactions(header: BlockHeader): Seq[TransactionEntity] = @@ -91,6 +94,9 @@ class ListBlocksReadState( timestamp = header.timestamp, chainFrom = new GroupIndex(1), chainTo = new GroupIndex(3), + version = 1, + networkId = 0, + scriptOpt = Some(Random.alphanumeric.take(10).mkString), gasAmount = 0, gasPrice = U256.unsafe(0), order = 0,