From fc7943ae4281444010cf23e6e3d17c698ae6895a Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 19 Sep 2024 16:20:35 +0300 Subject: [PATCH 1/6] docs: Voting ledger design (#709) * add initial ledger schema * add blake tags specefication * update block structure * cleanup * update * fix * fix CI * fix spelling * fix * fix * wip * fix * wip * add image * fix * fix * wip * wip * fix * fix * wip * fix * fix * fix * fix CI * fix cddl * fix spelling * Update docs/src/architecture/08_concepts/immutable_ledger/ledger.md Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/architecture/08_concepts/immutable_ledger/ledger.md Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/architecture/08_concepts/immutable_ledger/ledger.md Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * fix * cleanup * update from feedback --------- Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Steven Johnson Co-authored-by: Oleksandr Prokhorenko --- cspell.json | 1 + .../08_concepts/immutable_ledger/.pages | 3 + .../immutable_ledger/cddl/block.cddl | 31 + .../cddl/genesis_to_prev_hash.cddl | 18 + .../images/ledger_schema.excalidraw | 897 ++++++++++++++++++ .../immutable_ledger/images/ledger_schema.svg | 13 + .../images/temporary_chain.excalidraw | 486 ++++++++++ .../images/temporary_chain.svg | 13 + .../08_concepts/immutable_ledger/ledger.md | 169 ++++ .../src/catalyst-standards/cbor_tags/blake.md | 99 ++ 10 files changed, 1730 insertions(+) create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/.pages create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/cddl/block.cddl create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/cddl/genesis_to_prev_hash.cddl create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.excalidraw create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.svg create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.excalidraw create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.svg create mode 100644 docs/src/architecture/08_concepts/immutable_ledger/ledger.md create mode 100644 docs/src/catalyst-standards/cbor_tags/blake.md diff --git a/cspell.json b/cspell.json index c42691faf8..fca0d3ca13 100644 --- a/cspell.json +++ b/cspell.json @@ -157,6 +157,7 @@ ".config/dictionaries/cspell/markdown/cspell-ext.json" ], "ignorePaths": [ + "*.excalidraw", ".config/dictionaries/**", "tmp/**", "cspell.json", diff --git a/docs/src/architecture/08_concepts/immutable_ledger/.pages b/docs/src/architecture/08_concepts/immutable_ledger/.pages new file mode 100644 index 0000000000..088320ec00 --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/.pages @@ -0,0 +1,3 @@ +title: Immutable Ledger +arrange: + - ledger.md \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/cddl/block.cddl b/docs/src/architecture/08_concepts/immutable_ledger/cddl/block.cddl new file mode 100644 index 0000000000..f47e712198 --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/cddl/block.cddl @@ -0,0 +1,31 @@ +block = [ + block_header, + block_data: ~encoded-cbor, ; deterministically encoded CBOR + validator_signature, +] + +block_header = [ + chain_id: ULID, + height: int, + timestamp: ~#6.1(uint .ge 1722470400), ; Epoch-based date/time + prev_block_id: hash_bytes, ; hash of the previous block + ?ledger_type: UUID, + ?purpose_id: ULID / UUID, + ?validator, + ~metadata, +] + +UUID = #6.37(bytes) ; UUID type +ULID = #6.32780(bytes) ; ULID type + +hash_bytes = ( + #6.32781(bytes) \ ; Blake3 hash + #6.32782(bytes) \ ; Blake2b hash + #6.32783(bytes) ; Blake2s hash +) +kid = hash_bytes ; hash of the x509/c509 certificate + +validator = (kid / [2* kid]) +metadata = [ *Any ] + +validator_signature = (bytes / [2* bytes]) diff --git a/docs/src/architecture/08_concepts/immutable_ledger/cddl/genesis_to_prev_hash.cddl b/docs/src/architecture/08_concepts/immutable_ledger/cddl/genesis_to_prev_hash.cddl new file mode 100644 index 0000000000..f6b22766eb --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/cddl/genesis_to_prev_hash.cddl @@ -0,0 +1,18 @@ +genesis_to_prev_hash = [ + chain_id: ULID, + timestamp: ~#6.1(uint .ge 1722470400), ; Epoch-based date/time + ledger_type: UUID, + purpose_id: ULID / UUID, + validator, +] + +UUID = #6.37(bytes) ; UUID type +ULID = #6.32780(bytes) ; ULID type + +validator = (kid / [2* kid]) +kid = hash_bytes ; hash of the x509/c509 certificate +hash_bytes = ( + #6.32781(bytes) \ ; Blake3 hash + #6.32782(bytes) \ ; Blake2b hash + #6.32783(bytes) ; Blake2s hash +) \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.excalidraw b/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.excalidraw new file mode 100644 index 0000000000..b0357b95bd --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.excalidraw @@ -0,0 +1,897 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "LZaz6nUSvw4D3MKiGf-6N", + "type": "rectangle", + "x": 409, + "y": 612, + "width": 126.00000000000003, + "height": 106, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b1I", + "roundness": { + "type": 3 + }, + "seed": 998749129, + "version": 180, + "versionNonce": 488028033, + "isDeleted": false, + "boundElements": [ + { + "id": "60WFwdimNvf7qHAl1Qx-l", + "type": "arrow" + } + ], + "updated": 1724062455821, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 448, + "versionNonce": 1870983471, + "index": "b1m", + "isDeleted": false, + "id": "2y_gKpt1yYgzb2ulJxSIH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 418.6477595119575, + "y": 188.42732687658784, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 115255241, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "OjDpkXlr89sdrgoJXWZKl", + "type": "arrow" + }, + { + "id": "yWC7J7W90hNhY3n8YeI0R", + "type": "arrow" + } + ], + "updated": 1724183543948, + "link": null, + "locked": false + }, + { + "id": "yWC7J7W90hNhY3n8YeI0R", + "type": "arrow", + "x": 470.7966443874382, + "y": 312.4273268765878, + "width": 1.7887145032737521, + "height": 82.51369398294639, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b1o", + "roundness": { + "type": 2 + }, + "seed": 1642089769, + "version": 800, + "versionNonce": 912563369, + "isDeleted": false, + "boundElements": null, + "updated": 1723817518325, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -1.7887145032737521, + 82.51369398294639 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "2y_gKpt1yYgzb2ulJxSIH", + "focus": 0.1536117236497505, + "gap": 18.000000000000014, + "fixedPoint": null + }, + "endBinding": { + "elementId": "_Mqz3jLZxw94Y9d_DmU0j", + "focus": -0.05650178014706318, + "gap": 9.60349941250422, + "fixedPoint": null + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "type": "rectangle", + "version": 289, + "versionNonce": 1513389985, + "index": "b1v", + "isDeleted": false, + "id": "8AxtoYDOM72Qp47EdPXHe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 627.5, + "y": 609.2004468287341, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 150552359, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "-aKd0G117FN4NQt4vAhp4", + "type": "arrow" + }, + { + "id": "OjcadhzxebNls4hpPSNFu", + "type": "arrow" + }, + { + "id": "EIWoqkyVxQEyenX_x-Vw8", + "type": "arrow" + } + ], + "updated": 1724183567733, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 519, + "versionNonce": 1372912463, + "index": "b1x", + "isDeleted": false, + "id": "0BQ-1JHgWMGHcu8wZpeFv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 637.1477595119575, + "y": 185.62777370532197, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 181592423, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "-aKd0G117FN4NQt4vAhp4", + "type": "arrow" + } + ], + "updated": 1724183555017, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1207, + "versionNonce": 1062648175, + "index": "b1z", + "isDeleted": false, + "id": "-aKd0G117FN4NQt4vAhp4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 690.1406383907372, + "y": 308.62777370532194, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.9800121696592896, + "height": 86.86891320320751, + "seed": 332985255, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724183555019, + "link": null, + "locked": false, + "startBinding": { + "elementId": "0BQ-1JHgWMGHcu8wZpeFv", + "focus": 0.16976697626318282, + "gap": 17.000000000000014, + "fixedPoint": null + }, + "endBinding": { + "elementId": "JkGOAkU87OVn06Dh_dfoC", + "focus": 0.026898977589213263, + "gap": 6.248280192243101, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.9800121696592896, + 86.86891320320751 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 506, + "versionNonce": 1834369199, + "index": "b2k", + "isDeleted": false, + "id": "6XEgzMFi4hOd4lH063yLo", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 179.33426655457095, + "y": 189.49632349854818, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 491506505, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1724183542834, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 547, + "versionNonce": 178222087, + "index": "b2o", + "isDeleted": false, + "id": "_Mqz3jLZxw94Y9d_DmU0j", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 408.2753527268211, + "y": 404.5445202720384, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 163039495, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "yWC7J7W90hNhY3n8YeI0R", + "type": "arrow" + }, + { + "id": "60WFwdimNvf7qHAl1Qx-l", + "type": "arrow" + } + ], + "updated": 1723817524427, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 600, + "versionNonce": 29665703, + "index": "b2p", + "isDeleted": false, + "id": "JkGOAkU87OVn06Dh_dfoC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 626.7753527268211, + "y": 401.74496710077256, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 1040650279, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "-aKd0G117FN4NQt4vAhp4", + "type": "arrow" + }, + { + "id": "OjcadhzxebNls4hpPSNFu", + "type": "arrow" + } + ], + "updated": 1723817533761, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 873, + "versionNonce": 1953822465, + "index": "b2r", + "isDeleted": false, + "id": "60WFwdimNvf7qHAl1Qx-l", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 470.4895212990359, + "y": 522.509716928435, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.7887145032737521, + "height": 85.22413242346852, + "seed": 339776265, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724062426444, + "link": null, + "locked": false, + "startBinding": { + "elementId": "_Mqz3jLZxw94Y9d_DmU0j", + "focus": -0.00901044815406656, + "gap": 11.965196656396529, + "fixedPoint": null + }, + "endBinding": { + "elementId": "LZaz6nUSvw4D3MKiGf-6N", + "focus": -0.07020664971080574, + "gap": 4.266150648096527, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.7887145032737521, + 85.22413242346852 + ] + ], + "elbowed": false + }, + { + "type": "arrow", + "version": 942, + "versionNonce": 671820719, + "index": "b2s", + "isDeleted": false, + "id": "OjcadhzxebNls4hpPSNFu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 691.5227670213574, + "y": 513.549505359382, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.7623687083646473, + "height": 94.65094146935212, + "seed": 916830311, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724062426445, + "link": null, + "locked": false, + "startBinding": { + "elementId": "JkGOAkU87OVn06Dh_dfoC", + "focus": -0.04442061726814883, + "gap": 5.804538258609455, + "fixedPoint": null + }, + "endBinding": { + "elementId": "8AxtoYDOM72Qp47EdPXHe", + "focus": -0.02727222461108075, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.7623687083646473, + 94.65094146935212 + ] + ], + "elbowed": false + }, + { + "type": "arrow", + "version": 914, + "versionNonce": 472300769, + "index": "b3R", + "isDeleted": false, + "id": "fqZcWUsThJ-rpdspkAuJr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 238.97131516756298, + "y": 302.7762905138317, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.6466637127085164, + "height": 82.51369398294639, + "seed": 386853647, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724062504444, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "_MIQzK0BU_NTy7Fp2Zv98", + "focus": -0.05650178014706404, + "gap": 9.60349941250422, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.6466637127085164, + 82.51369398294639 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 596, + "versionNonce": 321717505, + "index": "b3S", + "isDeleted": false, + "id": "_MIQzK0BU_NTy7Fp2Zv98", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 174.97228126065949, + "y": 394.89348390928234, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 1866248495, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "fqZcWUsThJ-rpdspkAuJr", + "type": "arrow" + } + ], + "updated": 1724062504444, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 361, + "versionNonce": 811994433, + "index": "b3T", + "isDeleted": false, + "id": "S0X6nWW1CJo4M_I62jOld", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 625.6836663780216, + "y": 819.1309366933118, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 130846191, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "EIWoqkyVxQEyenX_x-Vw8", + "type": "arrow" + } + ], + "updated": 1724183571602, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1150, + "versionNonce": 1972682017, + "index": "b3U", + "isDeleted": false, + "id": "EIWoqkyVxQEyenX_x-Vw8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 688.2286911530929, + "y": 724.9577374702459, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.0070187505272088, + "height": 93.1731992230657, + "seed": 33749007, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724183571602, + "link": null, + "locked": false, + "startBinding": { + "elementId": "8AxtoYDOM72Qp47EdPXHe", + "focus": 0.011291554948495208, + "gap": 9.757290641511759, + "fixedPoint": null + }, + "endBinding": { + "elementId": "S0X6nWW1CJo4M_I62jOld", + "focus": -0.0321777046504664, + "gap": 1.0000000000002274, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.0070187505272088, + 93.1731992230657 + ] + ], + "elbowed": false + }, + { + "id": "UamvVhpP8556RAnEGTOMx", + "type": "text", + "x": 217.4034240988359, + "y": 106.97128723714539, + "width": 53.90400314331055, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3W", + "roundness": null, + "seed": 1070147407, + "version": 64, + "versionNonce": 1386620175, + "isDeleted": false, + "boundElements": null, + "updated": 1724184151611, + "link": null, + "locked": false, + "text": "Chain 1", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Chain 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "eZdaYUlv1ggIn_8Xr-Cg6", + "type": "text", + "x": 447.8714473764261, + "y": 102.6575947844726, + "width": 58.272003173828125, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3X", + "roundness": null, + "seed": 75853729, + "version": 62, + "versionNonce": 119664719, + "isDeleted": false, + "boundElements": null, + "updated": 1724184154061, + "link": null, + "locked": false, + "text": "Chain 2", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Chain 2", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "46e5QYSSn975Q1qBDqtE8", + "type": "text", + "x": 663.9206510497119, + "y": 101.1798525381862, + "width": 56.80000305175781, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3Y", + "roundness": null, + "seed": 1237485615, + "version": 58, + "versionNonce": 1498990575, + "isDeleted": false, + "boundElements": null, + "updated": 1724184163278, + "link": null, + "locked": false, + "text": "Chain 3", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Chain 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 63, + "versionNonce": 847436207, + "index": "b3j", + "isDeleted": false, + "id": "N5hTW0O5gndupZRM4ArBN", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 208.99215794348254, + "y": 132.25537493209856, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 74.1919937133789, + "height": 40, + "seed": 1846551439, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1724184148992, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 5, + "text": "Purpose id\n0xabc612", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Purpose id\n0xabc612", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 80, + "versionNonce": 966785615, + "index": "b3k", + "isDeleted": false, + "id": "5871CJITKyjY_BqMcB97n", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 444.3760250735433, + "y": 130.8373998289054, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 74.1919937133789, + "height": 40, + "seed": 1922001039, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1724184166295, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 5, + "text": "Purpose id\n0xabc612", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Purpose id\n0xabc612", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 86, + "versionNonce": 2101475407, + "index": "b3l", + "isDeleted": false, + "id": "nH3zTQCXz75-xQ1B_7Pa-", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 657.072290552514, + "y": 130.8373998289054, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 74.1919937133789, + "height": 40, + "seed": 504785281, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1724184161127, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 5, + "text": "Purpose id\n0xabc612", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Purpose id\n0xabc612", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.svg b/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.svg new file mode 100644 index 0000000000..1853e905b1 --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/images/ledger_schema.svg @@ -0,0 +1,13 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlX28hcdTAwMTL9nl/BYb5cdTAwMGWa7urqLd9YXHUwMDAzZIBcdTAwMTAgLO/N4Si2sFx1MDAxZIRkbLHOyX9/1Vx1MDAwNqzNcmxiJ5p5cU44tjaXuuveure75b/fLCwsJlx1MDAwZt1g8e3CYnDf8MNOs+ffLf7utt9cdTAwMDa9fieOaFx1MDAxN1xmPvfjm15jcGQ7Sbr9t3/8kZ7hNeKrp7OCMLhcbqKkT8f9hz4vLPw9+Et7Ok137p9n/qOKjlx1MDAwZW7vcE3svO+8u1hSu4NTXHUwMDA3XHUwMDA3vVx1MDAwNNNcdTAwMGJcdTAwMWGJXHUwMDFmtcIg3XVP25HZ4edcdTAwMDf6rDhcZj/fdZpJm7ZxUFx1MDAxZcu+xPCQdtBptVx1MDAxM3dcZlPDjU9f83aBXHK39JNefFx1MDAxOazGYdxzsfzGXHUwMDAz9y+N5LPfuGz14puoOTwm6flRv+v36NbT4y46YXiQPFxmrk7NR021WPiO45eYXHUwMDBi26vOoi9ttaOg38+dXHUwMDEzd/1GJ3lcdTAwMTjcV3pcdTAwMTcuwu5Wc9BcdTAwMTN/pTH1/Ktgy3VFdFx1MDAxM4bDzZ2oXHUwMDE5uFx1MDAwNl78zLdyX1x1MDAxNzWfv+6lXHUwMDFm005cdTAwMTLPW76mwVx1MDAwN4G7srVGo+WQ9lWaS9yw4tbdOFx1MDAxYeRcdTAwMTVcdTAwMWHDwDCR9lenv0b5lFxmLnrhh/0g7Vx1MDAwMlx1MDAxN9l6Mdey+ZbJOcWON+6anavd21x1MDAwYn29uVx1MDAxY/L9+6VweJe5vPN7vfhucbjn6/O7tP1uuk3/KSCuXHUwMDAxmVx1MDAwMpTSQNpcdTAwMTdhJ7osNm5cdTAwMTg3LtN7eJNpsyE4xuR92naIpqrtuNHMXHUwMDFhgZqP6tKrxVx0mvSpreDhvPW+m/CH09bjZ7hcdLfvXHUwMDBmtjbrktJlqFx1MDAwZUiBXHUwMDFiT6HW0krOrdQyR1x1MDAxMdxcdTAwMThcdTAwMGZBXHUwMDBiUEYrabTBmeP8tczzXGZcdTAwMTfOJUhcdMhfXHUwMDA33WlBOlx1MDAxNXT2vqx1L0/CnrH9Zq9cdTAwMTVvn1x1MDAxY5+9n1x1MDAxMDq/j7vuw/Gq3tbHlrV326dcIjKnwVx1MDAxNvs4XHUwMDEzSHIjJFxum4HKKyE5SaCFIPNpqZmnrVKIguhQXHUwMDE4yKWl4JBPy3I2edpcdTAwMTjNUTJBx2mZIZlhNlx1MDAxOfAkXHUwMDE3ylxua8CiXHUwMDEydlxmVGpU1Zpx4jrsh5a1eNKyXHUwMDA2VWWNK1x1MDAwNGasVqPqmmGVdY0qoVRCZE6bvK7l7iaX6MJwLSnZQU6R6MPN3bhTRH/6biHNmsGH4fu/flx1MDAxZnn0UnWqulcpSdPrlbBcdTAwMWP6/WQ1vrrquFx1MDAwNPnggizeUD/xe8lcbnVsJ2rl++9Zem5NUMdcXMbEjVx1MDAxYtdcdTAwMDDM41Io7lpUodWSycxBLb87KCF5WueYvU7nPlx1MDAxZmkpfYKo+e14z3euXHUwMDFmxZc/z+7vLJ7a5vna1Vx1MDAxMfsyKt4l5jGpJOPaUCCaKcFNKWLrKSbQWqTUY1xiMFW8g1x1MDAxNl52hNZcdTAwMGX8XHUwMDEypOhusvuKzFx1MDAxN4Sf47tZiFx1MDAxZDAlmFxyxVx1MDAwZaWTMKQy5Sig304udszyfVx1MDAxMp+u7e1o2O+iXm9+ONlcZuotdlx1MDAxNGgvL3BcdTAwMTSzXHUwMDFlMIaoXGZcdTAwMTgtsFx1MDAxOMvP1zeEKVx0Qqb9WVx1MDAxZn2z5L9vsneE/o1d3N1P8Ha53cVcdTAwMTnom70vXHK/2X68XHUwMDBmPu+GfWx3P1x1MDAxY+xu3Mzguutbx/H15cOn+/31hyA6Ob9f+nRnZqablNZcdTAwMTn3NU8rI3k1uoVcdTAwMDYqmVQoRqH7fnJ0s5X9Jb692TreebfZuDF3Z91g47bm6Fx1MDAxNtrjY6yM9Fxi/9RJmklcdTAwMDHc6tpB3XBpXHUwMDAxQfy7oD5cdTAwMDF0pKvIs4JOoaZmRk+A6UrcMFx1MDAwNVxunSpcdTAwMWOFm8fJcTO+oX62/q9cdTAwMDCOJVx1MDAxZIckhoywjPBRMFvM5IEzvzFcdTAwMDDmWbJcdTAwMDJcdTAwMWO4soqgYKwqXHUwMDAzxyjPKGO5XHUwMDAw0suMVHxcdTAwMTFGQlx1MDAwMIlcdTAwMWKQclx1MDAxZTAq+5tcdTAwMTKMvpXodopEn1a4j2ftvHBXZMbcf3BcIlx1MDAxOExW5j4rdz1/5b59+W5v+fLI6L1PXHUwMDExU2vt8+ZFvDo6YEZu35KD1NJYoO5PS9wwYuVcdTAwMDHSrVBcdTAwMWJcdTAwMDOg4IxPXHUwMDE18IT26bXifqausVx1MDAxYSnuVcJIerlcdTAwMTJcdTAwMWLPzHPITEUrXHKwXG4kXHUwMDBmy23GxVx1MDAwZtlcdTAwMTUuJ2dXdbLeetzZ6GB7r4nhJlHWw59xvVVcdNfWXHUwMDEzXHUwMDAyQSkpUWpmi7LEemiVXHUwMDAwcpxGUlx1MDAwNTKFyH66LEFLXHUwMDFlRGWs/Vx1MDAwZpYlY8cqgTLrx2hurNZcdTAwMGXaXHUwMDAwXHUwMDAwM3pUcseTJ/f4UYxaJjeSNlx1MDAwMC2FXHUwMDA0TexcZpznclx1MDAxYlx1MDAxOXpcdTAwMTJRXHUwMDAyo91M1HDyQFx0Jizan5Xa41x1MDAxNPd3XGbyjzXBc5vPXHUwMDFijKlcdTAwMDJcIsxMyY9cdTAwMDWkqlx1MDAxZTNcdTAwMDZLZKvZKFx1MDAwYlxm3cnxOF6b1Fx1MDAxMo8k5jw9XHUwMDA2j9zTSNVG05W0XHUwMDA2qWpcdTAwMDdIhq7UgP413DUrRFxuodXMZtgrvbXRolxujdxKQVx1MDAwNVx1MDAxMtUoa1xyvSnE31jmqqe1dvOYaKxcdTAwMDRcdTAwMGXWsuwwrjtfXHUwMDAyeJJZTSpcdTAwMWVcZlxuOT84TjJcdTAwMWQqPeol8lxmSFpUkYMuXHUwMDFiazJfxDB1M9ZuXHUwMDFkXHQoxGmE4LTGeqpcdTAwMTkmZlx1MDAxOVx1MDAxMVx1MDAxOWl5SbEpmbFmL86ae2TbuCtTStC7dLHPwsyc9fi1YoWINVx1MDAwM4pcdTAwMTQpXHUwMDEzmWFSYyli9MjAOFx1MDAxZoCGuYD1VFx1MDAwMf+TnPW35mOLKEmvV1wi42msdSWzWoTi1lx1MDAxN2alOm7I3vORrro/ObGOL0D1JFZluUf0qVx1MDAxNaUuXHUwMDE3mZR9XCJWLsh5WDKtRLnZ1SNzIFZcdTAwMDVCXHUwMDE5zYxQqFCP0DlcdTAwMTY90jRcdTAwMTY5KitcXCUoXHUwMDEyq+XKkFx1MDAxMeFzWcM0XHUwMDAzYn3VXCKJXHSJdeJcdTAwMDFAR1PE8cRT3GlbjsaUXHUwMDA3XHUwMDAwpWdcdTAwMTg6uSGNolx1MDAxNpfZ5VxiM+LV8dPehYBJhmtw2oc7YtUjVkf8q3m0XHUwMDAyXHUwMDFh7lVcdTAwMDLF3Hk0XHUwMDFkvi6tnSWpTIZIjeJR8XFyXHUwMDFlvbg+a1x1MDAxY1x1MDAxZvVcdTAwMGbb20u9brPfvVxcvtnu1ZxHySR4VPZcdTAwMDWXXFxpqcCm449Pkz/gOdFnXHUwMDE5UapcdTAwMTF8fpOm4FGSKCU0XHUwMDA3ylx1MDAxOIom7a5cdFbsvUhUQyWZsi1ccrM2TCpcdTAwMWR7zUKiXHUwMDE2XHUwMDExPYl43dp/fM9Wjs53XHUwMDBmXHUwMDFm9EZcdTAwMTfObq2poqzc8ihkZSn4Xcuj/lFcZladku41zcq82U2y2MpJXHUwMDE2XHUwMDAxXFzz7Fx1MDAxMrwsi1x1MDAxZEwzXGY9LltqOezFNVx1MDAxMolcdTAwMDFcdTAwMThcdTAwMGWKKWkxb7NcdTAwMDVcdTAwMTVcdTAwMWJjXHUwMDA1uultMtog6jdcdTAwMTBtXHUwMDE0aS1Tz5Ho8WUtl7vTP1x1MDAwMTI1Lb5cdTAwMWU7XCIzXHUwMDBlVsCO4ZxcYlxyxaghY3E4OXZcdTAwMGXYiYqOj/nqdow751tcbr7shc16Y4e6wCP7MeA5Q15K5aBjuPW4YJb2W0FcdTAwMGWldrOTXHUwMDE0nCF5bev4/Md811x1MDAxYmquXHUwMDE4zFxuONWLprisnGfhdjDLkF27lYHN0eSwXHUwMDE531D1XHUwMDE0zspcdTAwMTiPKo6y1EJcdTAwMDRcdTAwMTDIl1x1MDAxY+ojz0q3ZopkXHUwMDE0YGbcd/YjXHUwMDEwZF1cdTAwMTg3rvK7aVZjyrixwuNaXHUwMDEwxVx1MDAwMVx1MDAxOVx1MDAxZCV1XHUwMDExRILCtIzVTTW/JsurVPN323mSxpyD5VKiXHUwMDFkVGpq6lx1MDAxMdpYS01mSSH5KVx1MDAxMmNzXHUwMDE411x1MDAxZF9iXG5i3mlCrVx1MDAxOZKmJyVb1vI8R7lcdTAwMDB6uiVe/yg1X1xyXHUwMDE09ypC5DvF/Fx1MDAxM69cdTAwMWT5V7ef2t1cdTAwMGZGSvVxOVp/d7i3k1mD/UK+SXCfXHUwMDAx9sCVc+0huSySSNaY4rRcdTAwMTGVQefZwWhcdTAwMTBuoDrjg19YQVxuzzKkXCLKSdRwlllcZjkkXHUwMDA1XHUwMDE4x3D/38+7iePRz7vlU/tlylo7r4zltcWuSFSOOXHhJD/LLTiewYNtxJmOelx1MDAxNJ9myjdNSJeI1Fx1MDAwMKttv1x1MDAxMy3wTDfGUXLQeVx1MDAxY1x1MDAwNK5yWzf8q044XHUwMDE4cs9dZTnstFxcXHUwMDAzLIbBRZLTXHUwMDE1Safhh8PdSZxZkNGg69FcdTAwMTdcdTAwMDe9ct/EvU6rXHUwMDEz+eFhVYT+TVx1MDAxMn9cZvpPMSa9myB7/8HmUEF6IMdcdTAwMDA2OGv6p0fhLW+1tqJzc9JbWm2pSVx1MDAwMIuoPTdphFTxXHUwMDE1gsovvOBcZjziXHUwMDE0Mqba0CGQNuFcdTAwMTCvblx1MDAxZFx1MDAxNTi8amGcj/1cdTAwMDXYqVx1MDAwMHsyOWC1NNI9JTNcbq6VU23cTdZibqptZmglXHUwMDFiPFx1MDAwM7RC7dFcbrNGK6pA7p9cdTAwMWVcdTAwMWNEVst9fr2ydp2sm0nQSl7Xs0BlnjM3582L5ZWTXHUwMDE2XHUwMDE4PEMgXGY3qvwrKVJ5ZmBRmSRcdTAwMDLXZsRqil9grVx1MDAwNuvpXHUwMDE01ZVcdTAwMTRcdTAwMGVcdTAwMWGpuMxm5ctgaPUveqA11jI5j+qqXHUwMDA06Gl+XHUwMDFmoVx1MDAwMq+i9nhcdTAwMTWzw+toKGaIt3L1mCFpJVT2ya1MXCJlVkd/a4hhV7ZcdTAwMGaP2Z5sRc2b7tnHXHUwMDFkXFzurezWXHUwMDA2SVx1MDAxNXNzzHhkSLjUXHUwMDE2XHUwMDA1XHUwMDFhkPlFXHUwMDBlXFxcdTAwMDC1t3TeXVx1MDAwMPmEOS7n1Ohx9/hcdTAwMDbZXHIhdObh8iHfYYbdXsa0yXZK8lx1MDAxZjNYy5nbMcX4XHUwMDAxciRcdTAwMWV41fjB1KB0jUjYXGKChU7zv1x1MDAxMbv3PzdcdTAwMTTP1LxcdTAwMWZcdTAwMDHccd8/b1xmV/82XHUwMDE1SSed5/BcZoanePpHksJe3d46fP/w5fR85XqnsWJ1VHNcZiOiR5aAgWTa/bZOSnRPXHUwMDEwZp5cdTAwMTFaWEuin+rV/CamXoVgXHUwMDBiZEk4+7lcYiabbF+1XHUwMDAy6Vx1MDAxN4KnRXDl3DJwN8YhcXRcdTAwMTlcdTAwMGUnh3C0KVx1MDAxZVx1MDAwZvdXT1x1MDAxZbVcXLrf5yvn+oO/VHNcYpNz95hcdTAwMDa3XHUwMDA2hixcdTAwMDEvXHUwMDE24TojWDIk5lx1MDAwNTOD2bHvXHUwMDAwMOdTPVx1MDAxM/RcdTAwMGLAQ1x1MDAwML95XHUwMDFld170u92DhNp18WViYPG2XHUwMDEz3K2UU+i3i8HLTSVcdTAwMGXg7yBcdTAwMTVcZmZmvr75+j/Hc5tcdTAwMTQifQ== + + + + + Chain 1Chain 2Chain 3Purpose id0xabc612Purpose id0xabc612Purpose id0xabc612 \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.excalidraw b/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.excalidraw new file mode 100644 index 0000000000..6ac99526d6 --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.excalidraw @@ -0,0 +1,486 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 525, + "versionNonce": 706880097, + "index": "b1x", + "isDeleted": false, + "id": "0BQ-1JHgWMGHcu8wZpeFv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 637.1477595119575, + "y": 185.62777370532197, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 181592423, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "-aKd0G117FN4NQt4vAhp4", + "type": "arrow" + }, + { + "type": "text", + "id": "ztxOaqO2nACMyuCo41TT9" + } + ], + "updated": 1724411765588, + "link": null, + "locked": false + }, + { + "id": "ztxOaqO2nACMyuCo41TT9", + "type": "text", + "x": 659.7557574062446, + "y": 228.62777370532197, + "width": 80.78400421142578, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b1xV", + "roundness": null, + "seed": 1366198799, + "version": 15, + "versionNonce": 2089624047, + "isDeleted": false, + "boundElements": null, + "updated": 1724411768249, + "link": null, + "locked": false, + "text": "Final block", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0BQ-1JHgWMGHcu8wZpeFv", + "originalText": "Final block", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1361, + "versionNonce": 79857743, + "index": "b1z", + "isDeleted": false, + "id": "-aKd0G117FN4NQt4vAhp4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 700.1406383907372, + "y": 295.62777370532194, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.502788755468373, + "height": 78.86891320320751, + "seed": 332985255, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724411746124, + "link": null, + "locked": false, + "startBinding": { + "elementId": "0BQ-1JHgWMGHcu8wZpeFv", + "focus": 0.028074821332862033, + "gap": 4.000000000000014, + "fixedPoint": null + }, + "endBinding": { + "elementId": "MwGedbcYMTL-31ZrpCyid", + "focus": -0.03506656979648994, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.502788755468373, + 78.86891320320751 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 374, + "versionNonce": 967448481, + "index": "b3m", + "isDeleted": false, + "id": "vgLRytLdKqTx11SHt2RnV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 644.2635924814804, + "y": 580.2337821688373, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 1052207649, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "MrQ8EOYMz3pjVo4Eol_iQ", + "type": "arrow" + }, + { + "id": "jhCh3yXlSwnv-qmxRtreW", + "type": "arrow" + } + ], + "updated": 1724411736683, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 694, + "versionNonce": 1540247361, + "index": "b3n", + "isDeleted": false, + "id": "MwGedbcYMTL-31ZrpCyid", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 643.5389452083016, + "y": 372.7783024408757, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 1869053441, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "MrQ8EOYMz3pjVo4Eol_iQ", + "type": "arrow" + }, + { + "id": "-aKd0G117FN4NQt4vAhp4", + "type": "arrow" + } + ], + "updated": 1724411736683, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1239, + "versionNonce": 1498456623, + "index": "b3o", + "isDeleted": false, + "id": "MrQ8EOYMz3pjVo4Eol_iQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 708.2863595028377, + "y": 484.58284069948513, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.7623687083646473, + "height": 94.65094146935212, + "seed": 1554650593, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724411738220, + "link": null, + "locked": false, + "startBinding": { + "elementId": "MwGedbcYMTL-31ZrpCyid", + "focus": -0.04442061726814763, + "gap": 5.804538258609455, + "fixedPoint": null + }, + "endBinding": { + "elementId": "vgLRytLdKqTx11SHt2RnV", + "focus": -0.027272224611082207, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.7623687083646473, + 94.65094146935212 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 440, + "versionNonce": 547544097, + "index": "b3p", + "isDeleted": false, + "id": "3lEZu9SwS4PMBe7uAkrOJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 642.447258859502, + "y": 790.164272033415, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.00000000000003, + "height": 106, + "seed": 1502523841, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "jhCh3yXlSwnv-qmxRtreW", + "type": "arrow" + }, + { + "type": "text", + "id": "g1_1V7ammpJ8hJwbxdWZt" + } + ], + "updated": 1724411771407, + "link": null, + "locked": false + }, + { + "id": "g1_1V7ammpJ8hJwbxdWZt", + "type": "text", + "x": 663.6072548922168, + "y": 833.164272033415, + "width": 83.68000793457031, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3pV", + "roundness": null, + "seed": 1429902415, + "version": 27, + "versionNonce": 1902577071, + "isDeleted": false, + "boundElements": null, + "updated": 1724411778513, + "link": null, + "locked": false, + "text": "First block", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3lEZu9SwS4PMBe7uAkrOJ", + "originalText": "First block", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1415, + "versionNonce": 476258927, + "index": "b3q", + "isDeleted": false, + "id": "jhCh3yXlSwnv-qmxRtreW", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 705.5132368875218, + "y": 695.9910728103491, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.3406931967199398, + "height": 93.1731992230657, + "seed": 724881825, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1724411738220, + "link": null, + "locked": false, + "startBinding": { + "elementId": "vgLRytLdKqTx11SHt2RnV", + "focus": 0.013288735774937478, + "gap": 9.757290641511872, + "fixedPoint": null + }, + "endBinding": { + "elementId": "3lEZu9SwS4PMBe7uAkrOJ", + "focus": -0.03217770465046808, + "gap": 1.0000000000002274, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.3406931967199398, + 93.1731992230657 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 111, + "versionNonce": 743176097, + "index": "b3r", + "isDeleted": false, + "id": "NTjua3sL1oQNf6AZFW2RR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 666.1077893109996, + "y": 86.64251359178576, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 68.16000366210938, + "height": 20, + "seed": 646862113, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1724411784149, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 5, + "text": "Chain 25", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Chain 25", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 129, + "versionNonce": 638475137, + "index": "b3s", + "isDeleted": false, + "id": "ArYNPbKLizqJUIR7jSVNU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 661.2594288138016, + "y": 112.30006088250497, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 74.1919937133789, + "height": 40, + "seed": 858327297, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1724411784149, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 5, + "text": " tree id\n0xabc612", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": " tree id\n0xabc612", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.svg b/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.svg new file mode 100644 index 0000000000..d660b8017c --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/images/temporary_chain.svg @@ -0,0 +1,13 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1aa1fbSFx1MDAxMv2eX+Hj+Vx1MDAxYTT9fuRcdTAwMWJhQ1x1MDAxMlx1MDAwMiQ8XHUwMDA2NszMmSOsxlaQJSHL2DAn/32rZdDDkoxcdTAwMWTMjHd2TU6O6W61StV1b91S8eerTqeb3sWm+6bTNdOeXHUwMDFi+F7iTrqv7fitSUZ+XHUwMDE0wlx1MDAxNMl+XHUwMDFmReOkl61cdTAwMWOkaTx68/PPxVx1MDAxNU4vXHUwMDFhzq4ygVx1MDAxOZowXHUwMDFkwbpf4fdO58/s/9J9XHUwMDEy00vdsFx1MDAxZpjsgmyquFx1MDAxNSd8fvQwXG6z20oklEJIy3yBXHUwMDFmemZqt7zE02Izf/QvMFwiNVx1MDAxZcxcXLnByFx1MDAxNDN2qIveXHUwMDFlbeG9XHUwMDBm/fOD91x1MDAxZnpjNbmIze5tcfmVXHUwMDFmXHUwMDA0J+ldkNk6iuD5irlRmkTX5tz30lx1MDAwMcziufG2q5Jo3Fx1MDAxZoRmNKpcXFx1MDAxM8Vuz0/v7Fx1MDAxOEL56MwxbzrFiH1AQaWDmZRcXHOMNZeFj7LrXHUwMDE1d1x1MDAwNJFSUok4JbjkoZlhO1FcdTAwMTAl1rCfsLE/hWmXbu+6XHUwMDBm9oVeviZN3HBcdTAwMTS7XHRcdTAwMWNisW7y+MhEOKj8ofmSgfH7gzR7XHUwMDFjUdzfZMeAXHUwMDE15powUqy2N40/elmY/F74PnGH5qO9XCJcdTAwMWNcdTAwMDdB2YGh9+DAx3AqXHUwMDAyij6MfC+eyq5/N1x1MDAxZojlYCyFw5b7yUPvMZa7h+zwKGW324OY5U9eiVxcN0miSTef+f66ed/H5amZptWNZne8T6ef3ZvPJNzeObhcdTAwMWLvRFxmn57q0rZcdTAwMGbfXG63jGPPncUzloQxsFVwrlQ+XHUwMDFm+OH1vM+CqHddQOBVyd5cdTAwMWOQi6zJ92l8mFlQcu1IXHUwMDBlwShcdTAwMTlcdTAwMTJglqhcdTAwMDQlIaotKFx1MDAxZoNJIUcqhlx1MDAxMCNcdTAwMTgzwqWqx1x1MDAxMlmEjHVcdTAwMDX3XHUwMDFhXHUwMDEw70WpPaBcdTAwMWaC/EpQKFPeWbdcdTAwMTEhlfWPXHUwMDAwpEJgraTWXHKci1splyCl4WhcdTAwMTErcW4ru86jrmJHQ1xiK8L0XG4hXFxEpI1E8MCuXHUwMDFmukHn0q4qnWVcdTAwMTSmJ/69NVx1MDAxZYvK6K479Fx1MDAwM+t7XtlpO/D71lx03Vx1MDAxZZhtkkpOSn3Ib/mCoe955aTVg01dPzTJx2XSSpT4fWvw6Vwi691xXHUwMDFhXHUwMDFkm9HM/jRcdTAwMTmbsn/Mh5xhXHUwMDFkwlx1MDAxYlx1MDAxMV0lqqajplx1MDAwMrdcdTAwMWS21IpLyWhTrN0vn15cdTAwMTfz6d9ccrbm/CpcdTAwMTGC/IpcdTAwMDRVVCNgLFKlMj2XX9mL5VficESkUsCrTCgqXHUwMDFi0qtUjlx1MDAxMkpjSlx1MDAxMPyTvOSmXHUwMDE51iklcJKEXHUwMDE3cb7GZEueTraL8lx1MDAxNlx1MDAxM5iwclAvXHUwMDBi+lHqJulbiEg/7FdccntcdTAwMTCcy4AwY4Le2FqJXHUwMDFjRFx1MDAxNJJMXHUwMDExXGb+Ulx1MDAwMpxJS8v6blxmi1hV62BW3sifXHUwMDFh70vkh+nDXHUwMDEz1Fx1MDAxY2NC72mDXHUwMDBmJu+Nd9n7enC6v0XxRVx1MDAxMu/clVx1MDAxMFEyeFx1MDAwYiymXHUwMDFjXHTBhZZaMKU1q1x1MDAxOYxXMjBwR+lONFx1MDAxY/pcdTAwMTZO5ZVVr29bMlx1MDAxOVx1MDAxOLdcdTAwMTYp8IDluXnWie2OVflVfOtcdTAwMTRcYsx+yb///rpxdSsw7KdcdTAwMDaJYrealjLBZTR5Qlx1MDAxOC1VqVDJ5kdcdTAwMWaZVFx1MDAwYsmYYqqAZkGldLg8ld7294/v0n3v083pXHUwMDE045NcdTAwMGYpOVx1MDAwZc82vFJhzCGCWrWvMFOo8FGWekHyXHUwMDExSiWgXHUwMDBlarkyvW1KoYI4gVxiXHUwMDEyJWWyOZXKQXKk3n3+enBP429nXHUwMDExe1x1MDAxN1x1MDAwNX/4R8+qVGb7flx1MDAxYuxcZujdv4OTSXi7dTOcXHUwMDFlp4k5X27fXHUwMDFhvOqUXHUwMDBmylNcdTAwMTXO/8FSZSlEXG7dikjMXHUwMDE5XCJMltVPXHSS4fKQXFzM11x1MDAxYlxuSepwqjTjIOcpwtU6XHLEjiMljMNpIaDXXHJ8dyA0yC6Ipf8hSD7jpcRfXHLJ9lqD0Fq1meORacW4XHUwMDEwpKnaoNFcbnhcXHhcdTAwMDCbWm0oXHUwMDA3JCckSdA0kFx1MDAwNWVcdTAwMDWQIFx1MDAxY1x1MDAxY66IgnJcdTAwMDQknuL4XHUwMDA1k6Qj4Vx1MDAwNIRcdTAwMDKDqGCCNZVcdTAwMWKaOYIjzTBcdTAwMTOacoJJXHKfVpFxxPWLvNx7Zr1BXHUwMDE1Kb1CWn+9sZJ8Z4xcdTAwMTEkwDRcdTAwMDHSSIp6vcFcdTAwMWRcdTAwMTBMQNWEK1x1MDAwMS4vKrj11Vx1MDAxYosl5ZzBRMJcdTAwMGYhULRhZFx1MDAxZClrXHUwMDE2/3NcdTAwMGKOrXZw2E9cclx1MDAxNsV+NVx1MDAwMl5byVx1MDAwMVx1MDAxObqNUDmTXHUwMDFjplx1MDAxYpsjNF6eT2nw7mKsTyYn7MvBWyPH29fJ571N1zfEYUxcdTAwMDJoVMaoXHUwMDE1NpVcdTAwMWE5XHUwMDE4Vkhb4LPSm85NUTdgMCdUbaS6eUZh0KJunmqN9PFcdTAwMWb4TLrDYbynXHUwMDA2e5PLqXd+ka4mbiRmXHUwMDA1UT2vNdJsTb7PgtaIoI5AXHUwMDEwkkxpYmvdSkwqSptjMm+MwNVcbkJIasq4RFx1MDAxNNdD6f+NkdbGXGKNV2mMMKI11D71XHUwMDFliHWynFx1MDAxZsz1K1xcw6VEslRPrqsxXCIrmu+HXHUwMDFhI8ko3ZzGyFx1MDAxMyml3lx1MDAxOGmw/uVcdTAwMWIjXHJcdTAwMTHweNig1SC5aNKYW2+Wz62L6fTvRltbrcJcdTAwMWRcYkcrhJRcdTAwMDSdU2UyobmjNVx1MDAwNqZTXHUwMDE4UabnXHJbZ6lCbUFEsVx1MDAxNlx1MDAxMmtNdUOzWFx1MDAwM61KWFx1MDAwMZRLkSi/yZhhXHUwMDFk8KVcdTAwMTRWZFx1MDAxM1x1MDAxYiMvXagsrftB9sNxw2FT21xm1FSyoi+fy37b+JdEI1x1MDAwMajBWFx1MDAxNf2y9Vx1MDAxNSqLWWOuUKFcdTAwMDRoU1wiW4cyyJx1g3FFm1x1MDAxMVwiV2vl/JdcdTAwMTUubVCxn3mQXHUwMDE029W01Sp1S1VcdTAwMDOVeFx1MDAxNbf3m1x1MDAxOcVStJQsyfK0enj6bezS0T6Ojlx1MDAwZa/E9sXuOTk+3nBaXHUwMDE1QjhAm1LBMSGtdfWdrFx1MDAxMlx1MDAwZchDoF2uMahcdTAwMDEp5lxmW1x1MDAxZq1cblx1MDAwNUrUXHUwMDE2K0JcdTAwMTAwhC7+XHUwMDEznFx1MDAwN1x1MDAxZYVqWMFyvIZcdTAwMTc+lYmVXGJcdTAwMTMqplx1MDAxZvvzkZWFkHXizlx1MDAwMFx1MDAwNE2H8G5lJldAgblKXHUwMDE3XGKkNIrb1FHF4nkpVL/runRQK1xcSesrW1x1MDAwMUWqhJhshOtoebhuJ19cdTAwMGa/XFx+2vfvb/Z++Xgsv52cXHUwMDFk/rLxcLVcdTAwMWXVXGbyXCKmar6FgjFxKIBIIFx1MDAwNeJcdTAwMDKxXHUwMDE3/PtLyVx1MDAxY6wtqUts26jFYeVwZTW4Kq4o1Jolq/7xcFx1MDAwNWhcdTAwMTjT8b3fQjR1L3tcdTAwMDKTv1x1MDAxNriL7r8yhF895OSuXHUwMDFixycp+DVXTd1b30ze1kPop6vsY9+bZFx1MDAwNGBBZTLZ+v3V9/9cdTAwMDBcYqdBXHUwMDFlIn0= + + + + + Final blockFirst blockChain 25 tree id0xabc612 \ No newline at end of file diff --git a/docs/src/architecture/08_concepts/immutable_ledger/ledger.md b/docs/src/architecture/08_concepts/immutable_ledger/ledger.md new file mode 100644 index 0000000000..b2a5396679 --- /dev/null +++ b/docs/src/architecture/08_concepts/immutable_ledger/ledger.md @@ -0,0 +1,169 @@ +# Immutable Ledger Design + +--- + +Title: Immutable Ledger Design + +Status: Proposed + +Authors: + - Alex Pozhylenkov + +Created: 2024-08-19 + +--- + +## Abstract + +This document describes a specification of the immutable ledger for various purposes of project "Catalyst". + +## Motivation + +Project "Catalyst" requires a solution for storing people votes and any other data, +in a transparent, verifiable, scalable and immutable way. + +## Specification + +### Ledger structure + +![Ledger schema](images/ledger_schema.svg){ align=right } + +Ledger will be represented as a collection of distinct, unconnected chains, +processed and run in parallel. +The only common thing for all these chains will be a "tree" identifier, +so these chains will serve and form an overall ledger state. + +Obviously, given approach leads to data duplication, +as each chain, will not know anything about others. +And it also requires that the overall ledger state, +could be deterministically defined at any point of time, +considering potential transaction overlapping or duplication. + +To achieve an immutability of data inside each chain +Each particular chain, will be a common sequence of blocks. +To achieve an immutability of data inside each chain, +cryptographic hashing is applied. +So each block from the chain reference to the hash of previous one. +It is a widely used technic to prevent a modification of some data from previous blocks, +without affecting structure of the current one. + +The described approach allows to easily scale and increase throughput of the network on demand at any time, +just by starting to process new chains. + +
+ + +### Temporary chains + +![Temporary chain schema](images/temporary_chain.svg){ align=right } + +It's a common thing for blockchains to have a starting block (genesis), +but it's unusual to have a final block for a chain. +After which no any block could be produced. + +And that's a main distinguish for this Immutable Ledger design, +it has a final block. + +So any chain will be bounded by some period of time. +Which is well suited where it comes to process some temporary event e.g. voting. + +
+ + +### Block structure + + +??? note "Block CDDL definition: `block.cddl`" + + ```CDDL + {{ include_file('src/architecture/08_concepts/immutable_ledger/cddl/block.cddl', indent=4) }} + ``` + + +Header: + +* `chain_id` - unique identifier of the chain. +* `height` - block's height. + Also is used to identify the block type: *genesis*, *regular*, *final* + (in more details described in [validation section](#block-validation-rules)). +* `timestamp` - block's timestamp. +* `prev_block_id` - previous block hash. +* `ledger_type` - unique identifier of the ledger type. + In general, this is the way to strictly bound and specify `block_data` of the ledger for the specific `ledger_type`. + But such rules will be a part of the specific ledger type definition, + and not specified by this document. +* `purpose_id` - unique identifier of the purpose. + As it was stated before, + each Ledger instance will have a strict time boundaries, + so each of them will run for different purposes. + This is the way to distinguish them. +* `validator` - identifier or identifiers of the entity who was produced and processed a block. +* `metadata` - fully optional field, to add some arbitrary metadata to the block. + +Block: + +* `block_header` - block header described above, +* `block_data` - an array of some CBOR encoded data +* `validator_signature` - a signature or signatures of the validator's. + +### Block validation rules + +* `chain_id` **MUST** be the same as for the previous block (except for genesis). +* `height` **MUST** be incremented by `1` from the previous block height value (except for genesis and final block). + *Genesis* block **MUST** have `0` value. + *Final* block **MUST** hash be incremented by `1` from the previous block height and changed the sign to negative. + E.g. previous block height is `9` and the *Final* block height is `-10`. +* *Final* block is the last one for the specific chain and any other block could not be referenced to the *Final* one. + +* `timestamp` **MUST** be greater or equals than the `timestamp` of the previous block (except for genesis). +* `prev_block_id` **MUST** be a hash of the previous block bytes (except for genesis). + +* `ledger_type` **MUST** be the same as for the previous block if present (except for genesis). + **MANDATORY** field for *Genesis* and *Final* blocks. +* `purpose_id` **MUST** be the same as for the previous block if present (except for genesis). + **MANDATORY** field for *Genesis* and *Final* blocks. +* `validator` **MUST** be the same as for the previous block if present (except for genesis). + **MANDATORY** field for *Genesis* and *Final* blocks. +* `prev_block_id`'s CBOR tag value and `bstr` size **MUST** be the same as for the previous block (except for genesis). + Means that the hash function type and hash size itself must be the same. +* `prev_block_id` and `validator_signature` **MUST** use the same hash function, defined with the + `hash_bytes`. + +* `prev_block_id` for the *Genesis* block **MUST** be a hash of the `genesis_to_prev_hash` bytes. + +* `block_data` **MUST** be a [deterministically][CBOR-deterministically-encoded] encoded CBOR. + + +??? note "Genesis to previous block hash CDDL definition: `genesis_to_prev_hash.cddl`" + + ```CDDL + {{ include_file('src/architecture/08_concepts/immutable_ledger/cddl/genesis_to_prev_hash.cddl',indent=4) }} + ``` + + +#### Signature rules + +`validator_signature` +**MUST** be a signature of the hashed `block_header` bytes and the `block_data` bytes +(with the order the same as defined for `block`). +Signed by the validator's keys defined in the corresponding certificates referenced by the `validator`. +Signature algorithm is defined by the certificate. +The format and size of this field **MUST** be totally the same as `validator` field: + +* if `validator` is only one id => `validator_signature` contains only 1 signature; +* if `validator` is array => `validator_signature` contains an array with the same length; +* order of signatures from the `validator_signature`'s array corresponds to the validators order of `validator`'s array. + +## Rationale + +## Path to Active + +### Acceptance Criteria + + +### Implementation Plan + + + + +[CBOR-deterministically-encoded]: https://datatracker.ietf.org/doc/html/rfc8949#name-deterministically-encoded-c diff --git a/docs/src/catalyst-standards/cbor_tags/blake.md b/docs/src/catalyst-standards/cbor_tags/blake.md new file mode 100644 index 0000000000..71573b6c6c --- /dev/null +++ b/docs/src/catalyst-standards/cbor_tags/blake.md @@ -0,0 +1,99 @@ + + +# BLAKE2 and BLAKE3 for for CBOR + +This document specifies a CBOR [1] tags for BLAKE2 [2] and BLAKE3 [3] hash functions. + +## BLAKE3 + + Tag: 32781 + Data item: byte string + Semantics: Binary BLAKE3 hash value (https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf) + Point of contact: Steven Johnson , Alex Pozhylenkov + Description of semantics: + https://github.com/input-output-hk/catalyst-voices/tree/main/docs/src/catalyst-standards/cbor_tags/blake.md#BLAKE3 + +### Semantics + +Tag 32781 can be applied to a byte string (major type 2) to indicate that the byte string +is a binary BLAKE3 [2] hash value encoded in big-endian. +The length of the byte string will characterize the size of the hash function to be used e.g. BLAKE3-256, BLAKE3-512 etc. + +## BLAKE2b + + Tag: 32782 + Data item: byte string + Semantics: Binary BLAKE2b hash value (https://www.blake2.net/blake2.pdf) + Point of contact: Steven Johnson , Alex Pozhylenkov + Description of semantics: + https://github.com/input-output-hk/catalyst-voices/tree/main/docs/src/catalyst-standards/cbor_tags/blake.md#BLAKE2b + +### Semantics + +Tag 32782 can be applied to a byte string (major type 2) to indicate that the byte string +is a binary BLAKE2b [3] hash value encoded in big-endian. +The length of the byte string will characterize the size of the hash function to be used e.g. BLAKE2b-256, BLAKE2b-512 etc. + +## BLAKE2s + + Tag: 32783 + Data item: byte string + Semantics: Binary BLAKE2s hash value (https://www.blake2.net/blake2.pdf) + Point of contact: Steven Johnson , Alex Pozhylenkov + Description of semantics: + https://github.com/input-output-hk/catalyst-voices/tree/main/docs/src/catalyst-standards/cbor_tags/blake.md#BLAKE2s + +### Semantics + +Tag 32783 can be applied to a byte string (major type 2) to indicate that the byte string +is a binary BLAKE2s [3] hash value encoded in big-endian. +The length of the byte string will characterize the size of the hash function to be used e.g. BLAKE2s-256, BLAKE2s-512 etc. + +## BLAKE2bp + + Tag: 32784 + Data item: byte string + Semantics: Binary BLAKE2bp hash value (https://www.blake2.net/blake2.pdf) + Point of contact: Steven Johnson , Alex Pozhylenkov + Description of semantics: + https://github.com/input-output-hk/catalyst-voices/tree/main/docs/src/catalyst-standards/cbor_tags/blake.md#BLAKE2bp + +### Semantics + +Tag 32784 can be applied to a byte string (major type 2) to indicate that the byte string +is a binary BLAKE2bp [3] hash value encoded in big-endian. +The length of the byte string will characterize the size of the hash function to be used e.g. BLAKE2bp-256, BLAKE2bp-512 etc. + +## BLAKE2sp + + Tag: 32785 + Data item: byte string + Semantics: Binary BLAKE2sp hash value (https://www.blake2.net/blake2.pdf) + Point of contact: Steven Johnson , Alex Pozhylenkov + Description of semantics: + https://github.com/input-output-hk/catalyst-voices/tree/main/docs/src/catalyst-standards/cbor_tags/blake.md#BLAKE2s + +### Semantics + +Tag 32785 can be applied to a byte string (major type 2) to indicate that the byte string +is a binary BLAKE2sp [3] hash value encoded in big-endian. +The length of the byte string will characterize the size of the hash function to be used e.g. BLAKE2sp-256, BLAKE2sp-512 etc. + +## References + + +[1] [C. Bormann, and P. Hoffman. "Concise Binary Object Representation (CBOR)". RFC 8949, October 2020.][RFC 8949] + +[2] [J. O'Conor, J-P. Aumasson, S. Neves, Z. Wilcox-O'Hearn. "BLAKE3 one function, fast everywhere".][BLAKE3] + +[3] [J-P. Aumasson, S. Neves, Z. Wilcox-O'Hearn., C. Winnerlein. "BLAKE2: simpler, smaller, fast as MD5". January 2013.][BLAKE2] + + +## Authors + +* Steven Johnson +* Alex Pozhylenkov + +[RFC 8949]: https://datatracker.ietf.org/doc/html/rfc8949 +[BLAKE3]: https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf +[BLAKE2]: https://www.blake2.net/blake2.pdf From 42bee43e28a2125c989c4547cb44efc674021a45 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 20 Sep 2024 11:35:47 +0700 Subject: [PATCH 2/6] fix(docs): Backend API title corrected in rendered docs --- .../api/cat-gateway/stoplight_template.html | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/src/api/cat-gateway/stoplight_template.html b/docs/src/api/cat-gateway/stoplight_template.html index c726b3017f..651e0a32f3 100644 --- a/docs/src/api/cat-gateway/stoplight_template.html +++ b/docs/src/api/cat-gateway/stoplight_template.html @@ -1,21 +1,20 @@ - - - - Elements in HTML - - - - - - + + + + Catalyst Gateway API + + + + + + + + + + - \ No newline at end of file From 6e47284d162b5167a1a00b9fea27fc550f11f2cf Mon Sep 17 00:00:00 2001 From: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:20:13 +0200 Subject: [PATCH 3/6] refactor(cat-voices): Refactor application bootstrap to allow running it in integration tests (#848) * wip * refactor: app initialization logic in tests * chore: improve FPS * refactor: bootstrap * docs(cat-voices): bootstrap docs * refactor(cat-voices): cleanup * refactor(cat-voices): lints * docs(cat-voices): add more documentation * refactor(cat-voices): cleanup * refactor(cat-voices): cleanup * refactor(cat-voices): cleanup name --------- Co-authored-by: kukkok3 <93382903+kukkok3@users.noreply.github.com> --- .../integration_test/app_test.dart | 23 +++++++++ catalyst_voices/lib/configs/bootstrap.dart | 47 ++++++++++++++----- catalyst_voices/lib/configs/main_dev.dart | 2 +- catalyst_voices/lib/configs/main_preprod.dart | 2 +- catalyst_voices/lib/configs/main_prod.dart | 2 +- catalyst_voices/lib/configs/main_qa.dart | 2 +- catalyst_voices/lib/configs/main_web.dart | 2 +- 7 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 catalyst_voices/integration_test/app_test.dart diff --git a/catalyst_voices/integration_test/app_test.dart b/catalyst_voices/integration_test/app_test.dart new file mode 100644 index 0000000000..c2774be7e4 --- /dev/null +++ b/catalyst_voices/integration_test/app_test.dart @@ -0,0 +1,23 @@ +import 'package:catalyst_voices/app/view/app.dart'; +import 'package:catalyst_voices/configs/bootstrap.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('end-to-end test', () { + testWidgets('run app', (tester) async { + final args = await bootstrap(); + await tester.pumpWidget(App(routerConfig: args.routerConfig)); + // let the application load + await tester.pump(const Duration(seconds: 5)); + + // pump and settle every 100ms to simulate almost production-like FPS + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + // wait 10s until the test is finished + await Future.delayed(const Duration(seconds: 10)); + }); + }); +} diff --git a/catalyst_voices/lib/configs/bootstrap.dart b/catalyst_voices/lib/configs/bootstrap.dart index c30117c0d0..64c63f3a31 100644 --- a/catalyst_voices/lib/configs/bootstrap.dart +++ b/catalyst_voices/lib/configs/bootstrap.dart @@ -31,24 +31,33 @@ final class BootstrapArgs { } // TODO(damian-molinski): Add Isolate.current.addErrorListener -Future bootstrap([ +// +/// The entry point for Catalyst Voices, +/// initializes and runs the application. +/// +/// Should configure dependency injection, setup logger and do +/// all the things which are necessary before the actual app is run. +/// +/// You can customize the default app by providing +/// your own instance via [builder]. +Future bootstrapAndRun([ BootstrapWidgetBuilder builder = _defaultBuilder, ]) async { await runZonedGuarded( - () => _safeBootstrap(builder), + () => _safeBootstrapAndRun(builder), _reportUncaughtZoneError, ); } -Future _safeBootstrap(BootstrapWidgetBuilder builder) async { +Future _safeBootstrapAndRun(BootstrapWidgetBuilder builder) async { try { - await _doBootstrap(builder); + await _doBootstrapAndRun(builder); } catch (error, stack) { await _reportBootstrapError(error, stack); } } -Future _doBootstrap(BootstrapWidgetBuilder builder) async { +Future _doBootstrapAndRun(BootstrapWidgetBuilder builder) async { // There's no need to call WidgetsFlutterBinding.ensureInitialized() // since this is already done internally by SentryFlutter.init() // More info here: https://github.com/getsentry/sentry-dart/issues/2063 @@ -56,14 +65,25 @@ Future _doBootstrap(BootstrapWidgetBuilder builder) async { WidgetsFlutterBinding.ensureInitialized(); } - _loggingService - ..level = kDebugMode ? Level.ALL : Level.OFF - ..printLogs = kDebugMode; - FlutterError.onError = _reportFlutterError; PlatformDispatcher.instance.onError = _reportPlatformDispatcherError; - await Dependencies.instance.init(); + final args = await bootstrap(); + final app = await builder(args); + await _runApp(app); +} + +/// Initializes the application before it can be run. Should setup all +/// the things which are necessary before the actual app is run, +/// either via [runApp] or injected into a test environment during +/// integration tests. +/// +/// Initialization logic that is relevant for [runApp] scenario +/// only should be added to [_doBootstrapAndRun], not here. +Future bootstrap() async { + _loggingService + ..level = kDebugMode ? Level.ALL : Level.OFF + ..printLogs = kDebugMode; GoRouter.optionURLReflectsImperativeAPIs = true; setPathUrlStrategy(); @@ -76,10 +96,9 @@ Future _doBootstrap(BootstrapWidgetBuilder builder) async { Bloc.observer = AppBlocObserver(); - final args = BootstrapArgs(routerConfig: router); - final app = await builder(args); + await Dependencies.instance.init(); - await _runApp(app); + return BootstrapArgs(routerConfig: router); } Future _runApp(Widget app) async { @@ -112,6 +131,8 @@ Future _reportFlutterError(FlutterErrorDetails details) async { /// Platform Dispatcher Errors reporting bool _reportPlatformDispatcherError(Object error, StackTrace stack) { _platformDispatcherLogger.severe('Platform Error', error, stack); + + // return true to prevent default error handling return true; } diff --git a/catalyst_voices/lib/configs/main_dev.dart b/catalyst_voices/lib/configs/main_dev.dart index f24ad0bd1c..49d48e6de4 100644 --- a/catalyst_voices/lib/configs/main_dev.dart +++ b/catalyst_voices/lib/configs/main_dev.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; void main() async { - await bootstrap(); + await bootstrapAndRun(); } diff --git a/catalyst_voices/lib/configs/main_preprod.dart b/catalyst_voices/lib/configs/main_preprod.dart index f24ad0bd1c..49d48e6de4 100644 --- a/catalyst_voices/lib/configs/main_preprod.dart +++ b/catalyst_voices/lib/configs/main_preprod.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; void main() async { - await bootstrap(); + await bootstrapAndRun(); } diff --git a/catalyst_voices/lib/configs/main_prod.dart b/catalyst_voices/lib/configs/main_prod.dart index f24ad0bd1c..49d48e6de4 100644 --- a/catalyst_voices/lib/configs/main_prod.dart +++ b/catalyst_voices/lib/configs/main_prod.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; void main() async { - await bootstrap(); + await bootstrapAndRun(); } diff --git a/catalyst_voices/lib/configs/main_qa.dart b/catalyst_voices/lib/configs/main_qa.dart index f24ad0bd1c..49d48e6de4 100644 --- a/catalyst_voices/lib/configs/main_qa.dart +++ b/catalyst_voices/lib/configs/main_qa.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; void main() async { - await bootstrap(); + await bootstrapAndRun(); } diff --git a/catalyst_voices/lib/configs/main_web.dart b/catalyst_voices/lib/configs/main_web.dart index f24ad0bd1c..49d48e6de4 100644 --- a/catalyst_voices/lib/configs/main_web.dart +++ b/catalyst_voices/lib/configs/main_web.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; void main() async { - await bootstrap(); + await bootstrapAndRun(); } From 05c5c9c9a76e594b92378fba4aa3fa1fc9f854ba Mon Sep 17 00:00:00 2001 From: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Date: Fri, 20 Sep 2024 19:19:00 +0200 Subject: [PATCH 4/6] feat(cat-voices): alert dialog (#851) * feat(cat-voices): alert dialog * fix(cat-voices) pass isDismissible to alert dialog * refactor(cat-voices): cleanup * chore(cat-voices): tests --- .../lib/widgets/avatars/voices_avatar.dart | 17 +- .../widgets/modals/voices_alert_dialog.dart | 163 ++++++++++++++++++ .../widgets/modals/voices_desktop_dialog.dart | 3 - .../lib/widgets/modals/voices_dialog.dart | 8 +- .../widgets/modals/voices_info_dialog.dart | 10 +- catalyst_voices/lib/widgets/widgets.dart | 1 + .../lib/src/themes/catalyst.dart | 2 +- .../widgets/avatars/voices_avatar_test.dart | 40 +++-- .../lib/examples/voices_avatar_example.dart | 8 + .../lib/examples/voices_modals_example.dart | 53 +++++- 10 files changed, 268 insertions(+), 37 deletions(-) create mode 100644 catalyst_voices/lib/widgets/modals/voices_alert_dialog.dart diff --git a/catalyst_voices/lib/widgets/avatars/voices_avatar.dart b/catalyst_voices/lib/widgets/avatars/voices_avatar.dart index b2570c6ed5..8989efd60a 100644 --- a/catalyst_voices/lib/widgets/avatars/voices_avatar.dart +++ b/catalyst_voices/lib/widgets/avatars/voices_avatar.dart @@ -21,6 +21,9 @@ class VoicesAvatar extends StatelessWidget { /// The size of the avatar, expressed as the radius (half the diameter). final double radius; + /// The border around the widget. + final Border? border; + /// The callback called when the widget is tapped. final VoidCallback? onTap; @@ -32,15 +35,21 @@ class VoicesAvatar extends StatelessWidget { this.backgroundColor, this.padding = const EdgeInsets.all(8), this.radius = 20, + this.border, this.onTap, }); @override Widget build(BuildContext context) { - return CircleAvatar( - radius: radius, - backgroundColor: - backgroundColor ?? Theme.of(context).colorScheme.primaryContainer, + return Container( + width: radius * 2, + height: radius * 2, + decoration: BoxDecoration( + color: + backgroundColor ?? Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(radius), + border: border, + ), child: Material( type: MaterialType.transparency, child: InkWell( diff --git a/catalyst_voices/lib/widgets/modals/voices_alert_dialog.dart b/catalyst_voices/lib/widgets/modals/voices_alert_dialog.dart new file mode 100644 index 0000000000..654260c71f --- /dev/null +++ b/catalyst_voices/lib/widgets/modals/voices_alert_dialog.dart @@ -0,0 +1,163 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +/// An alert dialog similar to [AlertDialog] +/// but customized to the project needs. +/// +/// On extra small screens (mobile) it will fill the whole screen width, +/// on larger screens it will take [_width] amount +/// of horizontal space and be centered. +/// +/// The close (x) button will appear if the dialog [isDismissible]. +class VoicesAlertDialog extends StatelessWidget { + static const double _width = 360; + + /// The widget which appears at the top of the dialog next to the (x) button. + /// Usually a [Text] widget. + final Widget? title; + + /// The widget which appears below the [title], + /// usually a [VoicesAvatar] widget. + final Widget? icon; + + /// The widget appears below the [icon], is less prominent than the [title]. + /// Usually a [Text] widget. + final Widget? subtitle; + + /// The widget appears below the [subtitle], usually a [Text] widget, + /// can be multiline. + final Widget? content; + + /// The list of widgets which appear at the bottom of the dialog, + /// usually [VoicesFilledButton] or [VoicesTextButton]. + /// + /// [buttons] are separated with 8px of padding between each other + /// so you don't need to add your own padding. + final List buttons; + + /// Whether to show a (x) close button. + final bool isDismissible; + + const VoicesAlertDialog({ + super.key, + this.title, + this.icon, + this.subtitle, + this.content, + this.buttons = const [], + this.isDismissible = true, + }); + + @override + Widget build(BuildContext context) { + final title = this.title; + final icon = this.icon; + final subtitle = this.subtitle; + final content = this.content; + + return ResponsiveBuilder( + xs: double.infinity, + other: _width, + builder: (context, width) { + return Dialog( + alignment: Alignment.center, + child: SizedBox( + width: width, + child: Padding( + padding: const EdgeInsets.only(top: 10, bottom: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (title != null || isDismissible) + Row( + children: [ + // if widget is dismissible then show an invisible + // close button to reserve space on this side of the + // row so that the title is centered + if (isDismissible) + const Visibility( + visible: false, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: _CloseButton(), + ), + Expanded( + child: DefaultTextStyle( + style: Theme.of(context).textTheme.titleLarge!, + textAlign: TextAlign.center, + child: title ?? const SizedBox.shrink(), + ), + ), + if (isDismissible) const _CloseButton(), + ], + ), + if (icon != null) + Padding( + padding: const EdgeInsets.only( + top: 24, + left: 20, + right: 20, + ), + child: Center(child: icon), + ), + if (subtitle != null) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 20, + right: 20, + ), + child: DefaultTextStyle( + style: Theme.of(context).textTheme.titleSmall!, + textAlign: TextAlign.center, + child: subtitle, + ), + ), + if (content != null) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 20, + right: 20, + ), + child: DefaultTextStyle( + style: Theme.of(context).textTheme.bodyMedium!, + textAlign: TextAlign.center, + child: content, + ), + ), + if (buttons.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + ...buttons.separatedBy(const SizedBox(height: 8)), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class _CloseButton extends StatelessWidget { + const _CloseButton(); + + @override + Widget build(BuildContext context) { + return XButton( + onTap: () => Navigator.of(context).pop(), + ); + } +} diff --git a/catalyst_voices/lib/widgets/modals/voices_desktop_dialog.dart b/catalyst_voices/lib/widgets/modals/voices_desktop_dialog.dart index 3016043a40..8318dbbe20 100644 --- a/catalyst_voices/lib/widgets/modals/voices_desktop_dialog.dart +++ b/catalyst_voices/lib/widgets/modals/voices_desktop_dialog.dart @@ -62,9 +62,6 @@ class VoicesDesktopPanelsDialog extends StatelessWidget { Expanded( child: Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: theme.colors.elevationsOnSurfaceNeutralLv1White, - ), child: right, ), ), diff --git a/catalyst_voices/lib/widgets/modals/voices_dialog.dart b/catalyst_voices/lib/widgets/modals/voices_dialog.dart index da2b7de363..95b0cd4976 100644 --- a/catalyst_voices/lib/widgets/modals/voices_dialog.dart +++ b/catalyst_voices/lib/widgets/modals/voices_dialog.dart @@ -4,13 +4,17 @@ import 'package:flutter/material.dart'; /// meant to be extended. abstract final class VoicesDialog { /// Encapsulates single entry point. - static Future show( - BuildContext context, { + static Future show({ + required BuildContext context, required WidgetBuilder builder, + RouteSettings? routeSettings, + bool barrierDismissible = true, }) { return showDialog( context: context, builder: builder, + routeSettings: routeSettings, + barrierDismissible: barrierDismissible, ); } } diff --git a/catalyst_voices/lib/widgets/modals/voices_info_dialog.dart b/catalyst_voices/lib/widgets/modals/voices_info_dialog.dart index 1bb023866f..d81bd7b909 100644 --- a/catalyst_voices/lib/widgets/modals/voices_info_dialog.dart +++ b/catalyst_voices/lib/widgets/modals/voices_info_dialog.dart @@ -11,7 +11,7 @@ import 'package:flutter/material.dart'; /// Call [VoicesDialog.show] with [VoicesDesktopInfoDialog] in order /// to show it. class VoicesDesktopInfoDialog extends StatelessWidget { - final String title; + final Widget title; const VoicesDesktopInfoDialog({ super.key, @@ -26,10 +26,10 @@ class VoicesDesktopInfoDialog extends StatelessWidget { left: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: theme.textTheme.titleLarge - ?.copyWith(color: theme.colors.textOnPrimary), + DefaultTextStyle( + style: theme.textTheme.titleLarge! + .copyWith(color: theme.colors.textOnPrimary), + child: title, ), ], ), diff --git a/catalyst_voices/lib/widgets/widgets.dart b/catalyst_voices/lib/widgets/widgets.dart index 7b0172eefd..0b9e550ea8 100644 --- a/catalyst_voices/lib/widgets/widgets.dart +++ b/catalyst_voices/lib/widgets/widgets.dart @@ -39,6 +39,7 @@ export 'menu/voices_list_tile.dart'; export 'menu/voices_menu.dart'; export 'menu/voices_node_menu.dart'; export 'menu/voices_wallet_tile.dart'; +export 'modals/voices_alert_dialog.dart'; export 'modals/voices_desktop_dialog.dart'; export 'modals/voices_dialog.dart'; export 'modals/voices_info_dialog.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart index dae4e3f1da..52149626e6 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart @@ -315,7 +315,7 @@ ThemeData _buildThemeData( barrierColor: const Color(0x612A3D61), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), clipBehavior: Clip.hardEdge, - backgroundColor: voicesColorScheme.onSurfaceNeutralOpaqueLv0, + backgroundColor: voicesColorScheme.elevationsOnSurfaceNeutralLv1White, ), listTileTheme: ListTileThemeData( shape: const StadiumBorder(), diff --git a/catalyst_voices/test/widgets/avatars/voices_avatar_test.dart b/catalyst_voices/test/widgets/avatars/voices_avatar_test.dart index 1c98c6f849..757f804687 100644 --- a/catalyst_voices/test/widgets/avatars/voices_avatar_test.dart +++ b/catalyst_voices/test/widgets/avatars/voices_avatar_test.dart @@ -14,13 +14,12 @@ void main() { ), ); - // Verify if CircleAvatar is rendered with the correct default radius. - final circleAvatarFinder = find.byType(CircleAvatar); - expect(circleAvatarFinder, findsOneWidget); + // Verify if Container is rendered with the correct default radius. + final containerFinder = find.byType(Container); + expect(containerFinder, findsOneWidget); - final circleAvatarWidget = - tester.widget(circleAvatarFinder); - expect(circleAvatarWidget.radius, 20); + final containerWidget = tester.widget(containerFinder); + expect(containerWidget.constraints?.maxWidth, 40); // Verify the icon is rendered. expect(find.byIcon(Icons.person), findsOneWidget); @@ -40,11 +39,10 @@ void main() { ), ); - // Verify if CircleAvatar is rendered with the correct custom radius. - final circleAvatarFinder = find.byType(CircleAvatar); - final circleAvatarWidget = - tester.widget(circleAvatarFinder); - expect(circleAvatarWidget.radius, 30); + // Verify if Container is rendered with the correct custom radius. + final containerFinder = find.byType(Container); + final containerWidget = tester.widget(containerFinder); + expect(containerWidget.constraints?.maxWidth, 60); // Verify the Padding is applied correctly. final paddingFinder = find.ancestor( @@ -73,10 +71,12 @@ void main() { ); // Verify the background color is correctly applied. - final circleAvatarFinder = find.byType(CircleAvatar); - final circleAvatarWidget = - tester.widget(circleAvatarFinder); - expect(circleAvatarWidget.backgroundColor, backgroundColor); + final containerFinder = find.byType(Container); + final containerWidget = tester.widget(containerFinder); + expect( + (containerWidget.decoration! as BoxDecoration).color, + backgroundColor, + ); // Verify the foreground color is correctly applied to the icon. final iconThemeFinder = find.ancestor( @@ -132,10 +132,12 @@ void main() { ); // Verify the background color is from the theme's primaryContainer. - final circleAvatarFinder = find.byType(CircleAvatar); - final circleAvatarWidget = - tester.widget(circleAvatarFinder); - expect(circleAvatarWidget.backgroundColor, Colors.blueGrey); + final containerFinder = find.byType(Container); + final containerWidget = tester.widget(containerFinder); + expect( + (containerWidget.decoration! as BoxDecoration).color, + Colors.blueGrey, + ); // Verify the foreground color is from the theme's primary. final iconThemeFinder = find.byType(IconTheme); diff --git a/catalyst_voices/uikit_example/lib/examples/voices_avatar_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_avatar_example.dart index 53bbee07ec..5c1c42ae9a 100644 --- a/catalyst_voices/uikit_example/lib/examples/voices_avatar_example.dart +++ b/catalyst_voices/uikit_example/lib/examples/voices_avatar_example.dart @@ -22,6 +22,14 @@ class VoicesAvatarExample extends StatelessWidget { VoicesAvatar( icon: VoicesAssets.icons.check.buildIcon(), ), + VoicesAvatar( + backgroundColor: Colors.transparent, + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + icon: VoicesAssets.icons.check.buildIcon(), + ), VoicesAvatar( icon: const Text('A'), onTap: () {}, diff --git a/catalyst_voices/uikit_example/lib/examples/voices_modals_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_modals_example.dart index 304b0626ad..79aca47497 100644 --- a/catalyst_voices/uikit_example/lib/examples/voices_modals_example.dart +++ b/catalyst_voices/uikit_example/lib/examples/voices_modals_example.dart @@ -1,4 +1,8 @@ +import 'dart:async'; + import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; import 'package:flutter/material.dart'; class VoicesModalsExample extends StatelessWidget { @@ -12,21 +16,64 @@ class VoicesModalsExample extends StatelessWidget { appBar: AppBar(title: const Text('Modals')), body: Padding( padding: const EdgeInsets.all(16), - child: Column( + child: Wrap( + spacing: 16, + runSpacing: 16, children: [ VoicesFilledButton( child: const Text('Desktop info dialog'), onTap: () async { await VoicesDialog.show( - context, + context: context, builder: (context) { return const VoicesDesktopInfoDialog( - title: 'Desktop modal', + title: Text('Desktop modal'), ); }, ); }, ), + VoicesFilledButton( + child: const Text('Alert Dialog'), + onTap: () => unawaited( + VoicesDialog.show( + context: context, + builder: (context) { + return VoicesAlertDialog( + title: const Text('WARNING'), + icon: VoicesAvatar( + radius: 40, + backgroundColor: Colors.transparent, + icon: VoicesAssets.icons.exclamation.buildIcon( + size: 36, + color: Theme.of(context).colors.iconsError, + ), + border: Border.all( + color: Theme.of(context).colors.iconsError!, + width: 3, + ), + ), + subtitle: const Text('ACCOUNT CREATION INCOMPLETE!'), + content: const Text( + 'If attempt to leave without creating your keychain' + ' - account creation will be incomplete.\n\nYou are' + ' not able to login without completing your keychain.', + ), + buttons: [ + VoicesFilledButton( + child: const Text('Continue keychain creation'), + onTap: () => Navigator.of(context).pop(), + ), + VoicesTextButton( + child: const Text('Cancel anyway'), + onTap: () => Navigator.of(context).pop(), + ), + ], + ); + }, + ), + ), + ), ], ), ), From b96002e74d10a88054dd5e177f90c42e2fd19a6c Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Sat, 21 Sep 2024 08:01:10 +0700 Subject: [PATCH 5/6] feat(cat-gateway): Chain sync V2 - part 2 (#836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(docs): Fix up docs issues * fix(backend): Huge refactor to prep for scylladb config management * fix(backend): Clean up logging a little, and add build info logs as required for production. * Refactor and setup cassandra config/session * feat(backend): Index DB schema setup seems to work * WIP * fix(rust): Format fixes * fix(rust): Build fixes * fix(rust): Adjust index DB so we can index without querying, and can optimize on first detected spend. * fix(rust): add more docs * fix(rust): basic new follower integration * fix(rust): wip * fix(ci): Bump rust compiler version to match CI * ci(backend): Bump rust version to match CI * fix(backend): Fix code format and lints * feat(backend): simple new block indexer just to test the logic works * feat(gateway): Simple indexing with cassandra seems to work * refactor(backend): Remove lazy and once_cell in favor of new standard library replacements * fix(backend): WIP indexing for stake addresses and unstaked ada * fix(backend): indexing WIP * fix(backend): Add support for log control with env vars, default to mainnet, adjust `justfile` to properly select preprod and also refresh git dependencies. * feat(backend): Make local test scylla db run with 4 nodes, not 1 * fix(backend-lib): Add stop for cassandra db cluster * refactor(backend-lib): Remove c509-certificate because its moved to catalyst-libs * fix(backend): Remove dependencies from Workspace, and move into project * fix(backend): Use temporary cat-ci branch for rust builders * fix(backend): Remove obsolete common crates subdirectory * fix(backend): Don't use pre-packaged mithril snapshots in integration tests * fix(backend): Fix code so it builds with latest chain follower code. Also eliminates redundant logic now incorporated into chain follower. * fix(backend): Fix broken reference to catalyst libs * ci(ci): Bump all earthfiles to latest WIP cat-ci branch * fix(frontend-pkg): Ignore .dart_tool directory in frontend files checking markdown * fix(ci): Fix spelling * fix(spelling): Add more project words and properly sort list * fix(backend): Sync rust configs and add target to make it easier in future * fix(backend): Enable all features of Scylla for now. * fix(frontend-pkg): Fix markdown table having too many columns * ci(spelling): Fix spelling issues * fix(docs): Bump docs to latest WIP cat-ci version * feat(gateway): Add low resource scylla db instance for local testing * feat(gateway): Add and update developer convenience functions for backend * fix(backend): Fix code format * fix(backend): Fix spelling issues in CQL files * fix(spelling): Remove duplicates from the project words dictionary * fix(backend): Get the backend building properly with earthly. * feat(backend): remove obsoleted postgres logic for chain indexing * revert(event-db): Revert extension changes to sql files after fixing sqlfluff version * fix(frontend): Regenerate the dart api interface file, and add doing that to the pre-push just command * fix(backend): temporarily disable API tests * fix(backend): Also temporarily stop workflow consuming test reports that are disabled * fix(ci): Try and stop coveralls running for api-tests * ci(general): Replace temp CI branch with tagged release * feat: Add Handler for Permissionless Auth (#825) * docs(cips): Add Formal Defintion of auth token * fix(docs): Fix comments in cddl file * fix(docs): sig size * fix(docs): Rename CDDL for the auth token * docs(docs): Add auth-header documentation * docs(docs): Fix markdown line length error * docs(general): Fix spelling * fix(backend-lib): Bump to catalyst-libs tagged version * fix(backend): stub out obsolete code (to be removed in follow up PR). * fix(backend-lib): code format * fix(backend): remove unused crate dependencies * feat: auth token (#723) * feat(auth token encode and decode): permissionless auth * feat(auth token encode and decode): permissionless auth * feat(auth token encode and decode): permissionless auth * feat(auth token encode and decode): permissionless auth * feat(auth token encode and decode): permissionless auth * iron out tests * iron out tests * refactor(auth token encode and decode): ed25519 Signature cbor fields Sig over the preceding two fields - sig(cbor(kid), cbor(ulid)) * refactor(auth token encode and decode): ed25519 Signature cbor fields Sig over the preceding two fields - sig(cbor(kid), cbor(ulid)) * feat(cat security scheme): open api * feat(cat security scheme): open api * feat(mock cert state): given kid from bearer return pub key * feat(auth token): cache TTL * feat(auth token): cache TTL * feat(auth token): cache TT * ci(spell check): fix * ci(spell check): fix * ci(spell check): fix * refactor(clippy): housekeeping tidy * refactor(clippy): housekeeping tidy * refactor(clippy): housekeeping tidy * refactor(clippy): housekeeping tidy * fix(backend): Re-enable dependent crates used by this code * fix(backend): clippy lints * fix(backend): spelling --------- Co-authored-by: Steven Johnson Co-authored-by: Steven Johnson * feat: Update GET staked_ada endpoint to fetch from ScyllaDB (#728) * feat: get staked ada from scylladb * chore: revert justfile changes * chore: filter TXOs in rust instead of filtering in ScyllaDB query * fix(backend): spelling * fix(backend): Eliminate lint errors from Derived function * fix(backend): code format * fix(backend): Udate autogenerated dart code * chore(cat-voices): fix tests --------- Co-authored-by: Steven Johnson Co-authored-by: Steven Johnson Co-authored-by: Dominik Toton * feat: DB Indexing for CIP-36 registrations (#788) * feat: add schema for cip-36 registration tables * feat: index cip-36 by stake address * feat: index cip-36 registrations by vote key * fix: use TxiInserParams::new when adding txi data * fix: remove unused cfg attributes * fix: refactor Cip36RegistrationInsertQuery::new * fix(backend): Refactor queries and add multiple tables for cip36 registration indexes * fix(backend): Cip36 Primary key is stake key. Stake Key N->1 Vote Key * fix(backend): code format --------- Co-authored-by: Steven Johnson Co-authored-by: Steven Johnson * docs(general): Cleanup project dictionary * docs(spelling): Fix spelling * fix(backend): remove obsolete clippy lint cfg * docs(backend): Improve field documentation so its not ambiguous. * docs(backend): Fix comment * docs(backend): Improve comment * fix(backend): Vote Key index logic, and update comments * fix(backend): Earthfile needs to be executed from root of repo, to properly pick up secrets * fix(backend): make generic saturating value converter and use it instead of type specific ones * test(cat-gateway): Add tests for float conversion and better docs about functions limitations. * fix(cat-gateway): Developer lints in release mode, and also refer to correct local release binary * fix(cat-gateway): CIP36 index schema error * fix(cat-gateway): Cip36 indexing working, improve bad cassandra query reporting. --------- Co-authored-by: cong-or <60357579+cong-or@users.noreply.github.com> Co-authored-by: Felipe Rosa Co-authored-by: Dominik Toton Co-authored-by: Joaquín Rosales --- catalyst-gateway/Justfile | 16 +-- catalyst-gateway/bin/Cargo.toml | 2 +- .../bin/src/db/index/block/certs.rs | 38 +++++- .../db/index/block/cip36/cql/insert_cip36.cql | 4 +- .../cip36/cql/insert_cip36_for_vote_key.cql | 8 +- .../block/cip36/cql/insert_cip36_invalid.cql | 2 +- .../src/db/index/block/cip36/insert_cip36.rs | 22 +++- .../block/cip36/insert_cip36_for_vote_key.rs | 2 +- .../index/block/cip36/insert_cip36_invalid.rs | 24 +++- .../bin/src/db/index/block/cip36/mod.rs | 3 +- .../bin/src/db/index/block/mod.rs | 4 +- .../bin/src/db/index/block/txi.rs | 2 +- .../bin/src/db/index/block/txo/insert_txo.rs | 2 +- .../db/index/block/txo/insert_txo_asset.rs | 2 +- .../db/index/block/txo/insert_unstaked_txo.rs | 2 +- .../block/txo/insert_unstaked_txo_asset.rs | 2 +- .../bin/src/db/index/block/txo/mod.rs | 4 +- .../bin/src/db/index/queries/mod.rs | 16 ++- .../queries/staked_ada/update_txo_spent.rs | 2 +- .../index/schema/cql/cip36_registration.cql | 2 +- .../cql/cip36_registration_for_vote_key.cql | 2 +- .../schema/cql/cip36_registration_invalid.cql | 2 +- catalyst-gateway/bin/src/db/index/session.rs | 3 +- .../bin/src/service/utilities/convert.rs | 124 +++++++----------- 24 files changed, 169 insertions(+), 121 deletions(-) diff --git a/catalyst-gateway/Justfile b/catalyst-gateway/Justfile index e742d37910..a07d9b0c0e 100644 --- a/catalyst-gateway/Justfile +++ b/catalyst-gateway/Justfile @@ -22,19 +22,19 @@ code-format: # Lint the rust code code-lint: - cargo lintfix - cargo lint + cargo lintfix -r + cargo lint -r # Synchronize Rust Configs sync-cfg: - earthly +sync-cfg + cd .. && earthly ./catalyst-gateway+sync-cfg # Pre Push Checks pre-push: sync-cfg code-format code-lint license-check # Make sure we can actually build inside Earthly which needs to happen in CI. - earthly +check - earthly +build - earthly +package-cat-gateway + cd .. && earthly ./catalyst-gateway+check + cd .. && earthly ./catalyst-gateway+build + cd .. && earthly ./catalyst-gateway+package-cat-gateway # Build Local release build of catalyst gateway build-cat-gateway: code-format code-lint @@ -46,10 +46,10 @@ run-cat-gateway: build-cat-gateway CHAIN_FOLLOWER_SYNC_TASKS="16" \ RUST_LOG="error,cat-gateway=debug,cardano_chain_follower=debug,mithril-client=debug" \ CHAIN_NETWORK="Preprod" \ - ./catalyst-gateway/target/release/cat-gateway run --log-level debug + ./target/release/cat-gateway run --log-level debug # Run cat-gateway natively on mainnet run-cat-gateway-mainnet: build-cat-gateway CHAIN_FOLLOWER_SYNC_TASKS="1" \ RUST_LOG="error,cat-gateway=debug,cardano_chain_follower=debug,mithril-client=debug" \ - ./catalyst-gateway/target/release/cat-gateway run --log-level debug + ./target/release/cat-gateway run --log-level debug diff --git a/catalyst-gateway/bin/Cargo.toml b/catalyst-gateway/bin/Cargo.toml index 8d614a96b8..aed66bbb86 100644 --- a/catalyst-gateway/bin/Cargo.toml +++ b/catalyst-gateway/bin/Cargo.toml @@ -58,7 +58,7 @@ build-info = "0.0.38" ed25519-dalek = "2.1.1" scylla = { version = "0.14.0", features = ["cloud", "full-serialization"] } strum = { version = "0.26.3", features = ["derive"] } -# strum_macros = "0.26.4" +strum_macros = "0.26.4" openssl = { version = "0.10.66", features = ["vendored"] } num-bigint = "0.4.6" futures = "0.3.30" diff --git a/catalyst-gateway/bin/src/db/index/block/certs.rs b/catalyst-gateway/bin/src/db/index/block/certs.rs index 3c7ec9bcca..86240df12b 100644 --- a/catalyst-gateway/bin/src/db/index/block/certs.rs +++ b/catalyst-gateway/bin/src/db/index/block/certs.rs @@ -1,6 +1,6 @@ //! Index certs found in a transaction. -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; use cardano_chain_follower::MultiEraBlock; use pallas::ledger::primitives::{alonzo, conway}; @@ -12,7 +12,7 @@ use crate::{ queries::{FallibleQueryTasks, PreparedQueries, PreparedQuery, SizedBatch}, session::CassandraSession, }, - service::utilities::convert::u16_from_saturating, + service::utilities::convert::from_saturating, settings::CassandraEnvVars, }; @@ -37,6 +37,38 @@ pub(crate) struct StakeRegistrationInsertQuery { pool_delegation: MaybeUnset>, } +impl Debug for StakeRegistrationInsertQuery { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + let stake_address = match self.stake_address { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(ref v) => &hex::encode(v), + }; + let register = match self.register { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(v) => &format!("{v:?}"), + }; + let deregister = match self.deregister { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(v) => &format!("{v:?}"), + }; + let pool_delegation = match self.pool_delegation { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(ref v) => &hex::encode(v), + }; + + f.debug_struct("StakeRegistrationInsertQuery") + .field("stake_hash", &hex::encode(hex::encode(&self.stake_hash))) + .field("slot_no", &self.slot_no) + .field("txn", &self.txn) + .field("stake_address", &stake_address) + .field("script", &self.script) + .field("register", ®ister) + .field("deregister", &deregister) + .field("pool_delegation", &pool_delegation) + .finish() + } +} + /// TXI by Txn hash Index const INSERT_STAKE_REGISTRATION_QUERY: &str = include_str!("./cql/insert_stake_registration.cql"); @@ -130,7 +162,7 @@ impl CertInsertQuery { let (key_hash, pubkey, script) = match cred { pallas::ledger::primitives::conway::StakeCredential::AddrKeyhash(cred) => { let addr = block - .witness_for_tx(cred, u16_from_saturating(txn)) + .witness_for_tx(cred, from_saturating(txn)) .unwrap_or(default_addr); // Note: it is totally possible for the Registration Certificate to not be // witnessed. diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36.cql b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36.cql index 220954045c..1ecacb3493 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36.cql +++ b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36.cql @@ -8,7 +8,7 @@ INSERT INTO cip36_registration ( payment_address, is_payable, raw_nonce, - cip36, + cip36 ) VALUES ( :stake_address, :nonce, @@ -18,5 +18,5 @@ INSERT INTO cip36_registration ( :payment_address, :is_payable, :raw_nonce, - :cip36, + :cip36 ); diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_for_vote_key.cql b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_for_vote_key.cql index a09d36d3f5..b6d257f9c8 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_for_vote_key.cql +++ b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_for_vote_key.cql @@ -1,14 +1,14 @@ --- Index CIP-36 Registration (Valid) -INSERT INTO cip36_registration_for_stake_addr ( +-- Index CIP-36 Registration (For each Vote Key) +INSERT INTO cip36_registration_for_vote_key ( vote_key, stake_address, slot_no, txn, - valid, + valid ) VALUES ( :vote_key, :stake_address, :slot_no, :txn, - :valid, + :valid ); diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_invalid.cql b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_invalid.cql index 06162661fd..fac9b51d1a 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_invalid.cql +++ b/catalyst-gateway/bin/src/db/index/block/cip36/cql/insert_cip36_invalid.cql @@ -10,7 +10,7 @@ INSERT INTO cip36_registration_invalid ( nonce, cip36, signed, - error_report, + error_report ) VALUES ( :stake_address, :slot_no, diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36.rs b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36.rs index d346124998..771cb9b5d2 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36.rs +++ b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36.rs @@ -1,6 +1,6 @@ //! Insert CIP36 Registration Query -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; use cardano_chain_follower::Metadata::cip36::{Cip36, VotingPubKey}; use scylla::{frame::value::MaybeUnset, SerializeRow, Session}; @@ -37,6 +37,26 @@ pub(super) struct Params { cip36: bool, } +impl Debug for Params { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let payment_address = match self.payment_address { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(ref v) => &hex::encode(v), + }; + f.debug_struct("Params") + .field("stake_address", &self.stake_address) + .field("nonce", &self.nonce) + .field("slot_no", &self.slot_no) + .field("txn", &self.txn) + .field("vote_key", &self.vote_key) + .field("payment_address", &payment_address) + .field("is_payable", &self.is_payable) + .field("raw_nonce", &self.raw_nonce) + .field("cip36", &self.cip36) + .finish() + } +} + impl Params { /// Create a new Insert Query. pub fn new(vote_key: &VotingPubKey, slot_no: u64, txn: i16, cip36: &Cip36) -> Self { diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_for_vote_key.rs b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_for_vote_key.rs index 67a892d4f8..b7f0d48d83 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_for_vote_key.rs +++ b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_for_vote_key.rs @@ -16,7 +16,7 @@ const INSERT_CIP36_REGISTRATION_FOR_VOTE_KEY_QUERY: &str = include_str!("./cql/insert_cip36_for_vote_key.cql"); /// Insert CIP-36 Registration Invalid Query Parameters -#[derive(SerializeRow, Clone)] +#[derive(SerializeRow, Debug)] pub(super) struct Params { /// Voting Public Key vote_key: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_invalid.rs b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_invalid.rs index 0ee5a4e5b1..0ab3fd8122 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_invalid.rs +++ b/catalyst-gateway/bin/src/db/index/block/cip36/insert_cip36_invalid.rs @@ -1,6 +1,6 @@ //! Insert CIP36 Registration Query (Invalid Records) -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; use cardano_chain_follower::Metadata::cip36::{Cip36, VotingPubKey}; use scylla::{frame::value::MaybeUnset, SerializeRow, Session}; @@ -42,6 +42,28 @@ pub(super) struct Params { error_report: Vec, } +impl Debug for Params { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let cip36 = match self.cip36 { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(v) => &format!("{v:?}"), + }; + f.debug_struct("Params") + .field("stake_address", &self.stake_address) + .field("slot_no", &self.slot_no) + .field("txn", &self.txn) + .field("vote_key", &self.vote_key) + .field("payment_address", &self.payment_address) + .field("is_payable", &self.is_payable) + .field("raw_nonce", &self.raw_nonce) + .field("nonce", &self.nonce) + .field("cip36", &cip36) + .field("signed", &self.signed) + .field("error_report", &self.error_report) + .finish() + } +} + impl Params { /// Create a new Insert Query. pub fn new( diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs b/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs index aa7efe29b8..13d4c70b46 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs @@ -75,7 +75,8 @@ impl Cip36InsertQuery { vote_key, slot_no, txn_index, cip36, true, )); } - } else { + } else if cip36.stake_pk.is_some() { + // We can't index an error, if there is no stake public key. if cip36.voting_keys.is_empty() { self.invalid.push(insert_cip36_invalid::Params::new( None, diff --git a/catalyst-gateway/bin/src/db/index/block/mod.rs b/catalyst-gateway/bin/src/db/index/block/mod.rs index dd586ed6f8..775b55d502 100644 --- a/catalyst-gateway/bin/src/db/index/block/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/mod.rs @@ -14,7 +14,7 @@ use txi::TxiInsertQuery; use txo::TxoInsertQuery; use super::{queries::FallibleQueryTasks, session::CassandraSession}; -use crate::service::utilities::convert::i16_from_saturating; +use crate::service::utilities::convert::from_saturating; /// Add all data needed from the block into the indexes. pub(crate) async fn index_block(block: &MultiEraBlock) -> anyhow::Result<()> { @@ -34,7 +34,7 @@ pub(crate) async fn index_block(block: &MultiEraBlock) -> anyhow::Result<()> { // We add all transactions in the block to their respective index data sets. for (txn_index, txs) in block_data.txs().iter().enumerate() { - let txn = i16_from_saturating(txn_index); + let txn = from_saturating(txn_index); let txn_hash = txs.hash().to_vec(); diff --git a/catalyst-gateway/bin/src/db/index/block/txi.rs b/catalyst-gateway/bin/src/db/index/block/txi.rs index d3a37b3055..9dd4e0c8f9 100644 --- a/catalyst-gateway/bin/src/db/index/block/txi.rs +++ b/catalyst-gateway/bin/src/db/index/block/txi.rs @@ -14,7 +14,7 @@ use crate::{ }; /// Insert TXI Query and Parameters -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(crate) struct TxiInsertParams { /// Spent Transactions Hash txn_hash: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/txo/insert_txo.rs b/catalyst-gateway/bin/src/db/index/block/txo/insert_txo.rs index 7d9c0b6721..94837b5093 100644 --- a/catalyst-gateway/bin/src/db/index/block/txo/insert_txo.rs +++ b/catalyst-gateway/bin/src/db/index/block/txo/insert_txo.rs @@ -17,7 +17,7 @@ const INSERT_TXO_QUERY: &str = include_str!("./cql/insert_txo.cql"); /// Insert TXO Query Parameters /// (Superset of data to support both Staked and Unstaked TXO records.) -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(super) struct Params { /// Stake Address - Binary 28 bytes. 0 bytes = not staked. stake_address: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/txo/insert_txo_asset.rs b/catalyst-gateway/bin/src/db/index/block/txo/insert_txo_asset.rs index 9fa349237b..a42ea5b61e 100644 --- a/catalyst-gateway/bin/src/db/index/block/txo/insert_txo_asset.rs +++ b/catalyst-gateway/bin/src/db/index/block/txo/insert_txo_asset.rs @@ -15,7 +15,7 @@ const INSERT_TXO_ASSET_QUERY: &str = include_str!("./cql/insert_txo_asset.cql"); /// Insert TXO Asset Query Parameters /// (Superset of data to support both Staked and Unstaked TXO records.) -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(super) struct Params { /// Stake Address - Binary 28 bytes. 0 bytes = not staked. stake_address: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo.rs b/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo.rs index e27c7651c2..24957e92b3 100644 --- a/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo.rs +++ b/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo.rs @@ -14,7 +14,7 @@ const INSERT_UNSTAKED_TXO_QUERY: &str = include_str!("./cql/insert_unstaked_txo. /// Insert TXO Unstaked Query Parameters /// (Superset of data to support both Staked and Unstaked TXO records.) -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(super) struct Params { /// Transactions hash. txn_hash: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo_asset.rs b/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo_asset.rs index 8ac33aa129..78605f92ae 100644 --- a/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo_asset.rs +++ b/catalyst-gateway/bin/src/db/index/block/txo/insert_unstaked_txo_asset.rs @@ -15,7 +15,7 @@ const INSERT_UNSTAKED_TXO_ASSET_QUERY: &str = include_str!("./cql/insert_unstake /// Insert TXO Asset Query Parameters /// (Superset of data to support both Staked and Unstaked TXO records.) -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(super) struct Params { /// Transactions hash. txn_hash: Vec, diff --git a/catalyst-gateway/bin/src/db/index/block/txo/mod.rs b/catalyst-gateway/bin/src/db/index/block/txo/mod.rs index fc1ea2f306..9b4029fc3b 100644 --- a/catalyst-gateway/bin/src/db/index/block/txo/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/txo/mod.rs @@ -17,7 +17,7 @@ use crate::{ queries::{FallibleQueryTasks, PreparedQuery, SizedBatch}, session::CassandraSession, }, - service::utilities::convert::i16_from_saturating, + service::utilities::convert::from_saturating, settings::CassandraEnvVars, }; @@ -150,7 +150,7 @@ impl TxoInsertQuery { }; let staked = stake_address != NO_STAKE_ADDRESS; - let txo_index = i16_from_saturating(txo_index); + let txo_index = from_saturating(txo_index); if staked { let params = insert_txo::Params::new( diff --git a/catalyst-gateway/bin/src/db/index/queries/mod.rs b/catalyst-gateway/bin/src/db/index/queries/mod.rs index 89129f8d84..505918b58f 100644 --- a/catalyst-gateway/bin/src/db/index/queries/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/mod.rs @@ -4,9 +4,9 @@ pub(crate) mod staked_ada; -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; -use anyhow::bail; +use anyhow::{bail, Context}; use crossbeam_skiplist::SkipMap; use scylla::{ batch::Batch, prepared_statement::PreparedStatement, serialize::row::SerializeRow, @@ -26,7 +26,8 @@ use crate::settings::{CassandraEnvVars, CASSANDRA_MIN_BATCH_SIZE}; pub(crate) type SizedBatch = SkipMap>; /// All Prepared Queries that we know about. -#[allow(clippy::enum_variant_names, dead_code)] +#[derive(strum_macros::Display)] +#[allow(clippy::enum_variant_names)] pub(crate) enum PreparedQuery { /// TXO Insert query. TxoAdaInsertQuery, @@ -206,7 +207,7 @@ impl PreparedQueries { /// /// This will divide the batch into optimal sized chunks and execute them until all /// values have been executed or the first error is encountered. - pub(crate) async fn execute_batch( + pub(crate) async fn execute_batch( &self, session: Arc, cfg: Arc, query: PreparedQuery, values: Vec, ) -> FallibleQueryResults { @@ -238,7 +239,12 @@ impl PreparedQueries { bail!("No batch query found for size {}", chunk_size); }; let batch_query_statements = batch_query.value().clone(); - results.push(session.batch(&batch_query_statements, chunk).await?); + results.push( + session + .batch(&batch_query_statements, chunk) + .await + .context(format!("query={query}, chunk={chunk:?}"))?, + ); } Ok(results) diff --git a/catalyst-gateway/bin/src/db/index/queries/staked_ada/update_txo_spent.rs b/catalyst-gateway/bin/src/db/index/queries/staked_ada/update_txo_spent.rs index 21658d74e2..0fe0a60bcf 100644 --- a/catalyst-gateway/bin/src/db/index/queries/staked_ada/update_txo_spent.rs +++ b/catalyst-gateway/bin/src/db/index/queries/staked_ada/update_txo_spent.rs @@ -17,7 +17,7 @@ use crate::{ const UPDATE_TXO_SPENT_QUERY: &str = include_str!("../cql/update_txo_spent.cql"); /// Update TXO spent query params. -#[derive(SerializeRow)] +#[derive(SerializeRow, Debug)] pub(crate) struct UpdateTxoSpentQueryParams { /// TXO stake address. pub stake_address: Vec, diff --git a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration.cql b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration.cql index 17c6886e3b..f9303e0e6d 100644 --- a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration.cql +++ b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration.cql @@ -15,4 +15,4 @@ CREATE TABLE IF NOT EXISTS cip36_registration ( PRIMARY KEY (stake_address, nonce, slot_no, txn) ) -WITH CLUSTERING ORDER BY (nonce, DESC, slot_no DESC, txn DESC); +WITH CLUSTERING ORDER BY (nonce DESC, slot_no DESC, txn DESC); diff --git a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_for_vote_key.cql b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_for_vote_key.cql index 3ab03c8f1e..c3ba5f6dfc 100644 --- a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_for_vote_key.cql +++ b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_for_vote_key.cql @@ -1,7 +1,7 @@ -- Index of CIP-36 registrations searchable by Stake Address. -- Full registration data needs to be queried from the man cip36 registration tables. -- Includes both Valid and Invalid registrations. -CREATE TABLE IF NOT EXISTS cip36_registration_for_stake_addr ( +CREATE TABLE IF NOT EXISTS cip36_registration_for_vote_key ( -- Primary Key Data vote_key blob, -- 32 Bytes of Vote Key. stake_address blob, -- 32 Bytes of Stake Address. diff --git a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_invalid.cql b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_invalid.cql index e72eaf304e..626b9d90ac 100644 --- a/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_invalid.cql +++ b/catalyst-gateway/bin/src/db/index/schema/cql/cip36_registration_invalid.cql @@ -15,6 +15,6 @@ CREATE TABLE IF NOT EXISTS cip36_registration_invalid ( signed boolean, -- Signature validates. error_report list, -- List of serialization errors in the registration. - PRIMARY KEY (vote_key, slot_no, txn) + PRIMARY KEY (stake_address, slot_no, txn) ) WITH CLUSTERING ORDER BY (slot_no DESC, txn DESC); diff --git a/catalyst-gateway/bin/src/db/index/session.rs b/catalyst-gateway/bin/src/db/index/session.rs index 884f5a7a7a..300bc9d92a 100644 --- a/catalyst-gateway/bin/src/db/index/session.rs +++ b/catalyst-gateway/bin/src/db/index/session.rs @@ -1,6 +1,7 @@ //! Session creation and storage use std::{ + fmt::Debug, path::PathBuf, sync::{Arc, OnceLock}, time::Duration, @@ -122,7 +123,7 @@ impl CassandraSession { /// /// This will divide the batch into optimal sized chunks and execute them until all /// values have been executed or the first error is encountered. - pub(crate) async fn execute_batch( + pub(crate) async fn execute_batch( &self, query: PreparedQuery, values: Vec, ) -> FallibleQueryResults { let session = self.session.clone(); diff --git a/catalyst-gateway/bin/src/service/utilities/convert.rs b/catalyst-gateway/bin/src/service/utilities/convert.rs index f5733f1360..04f5424dc5 100644 --- a/catalyst-gateway/bin/src/service/utilities/convert.rs +++ b/catalyst-gateway/bin/src/service/utilities/convert.rs @@ -1,94 +1,60 @@ //! Simple general purpose utility functions. -/// Convert T to an i16. (saturate if out of range.) -#[allow(dead_code)] // Its OK if we don't use this general utility function. -pub(crate) fn i16_from_saturating>(value: T) -> i16 { - match value.try_into() { - Ok(value) => value, - Err(_) => i16::MAX, - } -} - -/// Convert an `` to `u16`. (saturate if out of range.) -#[allow(dead_code)] // Its OK if we don't use this general utility function. -pub(crate) fn u16_from_saturating< +/// Convert an `` to ``. (saturate if out of range.) +/// Note can convert any int to float, or f32 to f64 as well. +/// can not convert from float to int, or f64 to f32. +pub(crate) fn from_saturating< + R: Copy + num_traits::identities::Zero + num_traits::Bounded, T: Copy - + TryInto + + TryInto + std::ops::Sub + std::cmp::PartialOrd + num_traits::identities::Zero, >( value: T, -) -> u16 { - if value < T::zero() { - u16::MIN - } else { - match value.try_into() { - Ok(value) => value, - Err(_) => u16::MAX, - } +) -> R { + match value.try_into() { + Ok(value) => value, + Err(_) => { + // If we couldn't convert, its out of range for the destination type. + if value > T::zero() { + // If the number is positive, its out of range in the positive direction. + R::max_value() + } else { + // Otherwise its out of range in the negative direction. + R::min_value() + } + }, } } -/// Convert an `` to `usize`. (saturate if out of range.) -#[allow(dead_code)] // Its OK if we don't use this general utility function. -pub(crate) fn usize_from_saturating< - T: Copy - + TryInto - + std::ops::Sub - + std::cmp::PartialOrd - + num_traits::identities::Zero, ->( - value: T, -) -> usize { - if value < T::zero() { - usize::MIN - } else { - match value.try_into() { - Ok(value) => value, - Err(_) => usize::MAX, - } - } -} +#[cfg(test)] +mod tests { -/// Convert an `` to `u32`. (saturate if out of range.) -#[allow(dead_code)] // Its OK if we don't use this general utility function. -pub(crate) fn u32_from_saturating< - T: Copy - + TryInto - + std::ops::Sub - + std::cmp::PartialOrd - + num_traits::identities::Zero, ->( - value: T, -) -> u32 { - if value < T::zero() { - u32::MIN - } else { - match value.try_into() { - Ok(converted) => converted, - Err(_) => u32::MAX, - } - } -} + use super::*; -/// Convert an `` to `u64`. (saturate if out of range.) -#[allow(dead_code)] // Its OK if we don't use this general utility function. -pub(crate) fn u64_from_saturating< - T: Copy - + TryInto - + std::ops::Sub - + std::cmp::PartialOrd - + num_traits::identities::Zero, ->( - value: T, -) -> u64 { - if value < T::zero() { - u64::MIN - } else { - match value.try_into() { - Ok(converted) => converted, - Err(_) => u64::MAX, - } + #[test] + #[allow(clippy::float_cmp)] + fn from_saturating_tests() { + let x: u32 = from_saturating(0_u8); + assert!(x == 0); + let x: u32 = from_saturating(255_u8); + assert!(x == 255); + let x: i8 = from_saturating(0_u32); + assert!(x == 0); + let x: i8 = from_saturating(512_u32); + assert!(x == 127); + let x: i8 = from_saturating(-512_i32); + assert!(x == -128); + let x: u16 = from_saturating(-512_i32); + assert!(x == 0); + let x: f64 = from_saturating(0.0_f32); + assert!(x == 0.0); + let x: f64 = from_saturating(0_u32); + assert!(x == 0.0); + let x: f64 = from_saturating(65536_u32); + assert!(x == 65536.0_f64); + let x: f64 = from_saturating(i32::MIN); + assert!(x == -2_147_483_648.0_f64); } } From 50348cd8d83ec0386d9805ea5ee0c7472c20b4e0 Mon Sep 17 00:00:00 2001 From: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:16:14 +0200 Subject: [PATCH 6/6] feat(cat-voices): password strength indicator (#861) * feat(cat-voices): password strength calculator * feat(cat-voices): add password strength indicator widget * chore(cat-voices): reformat --- .../voices_password_strength_indicator.dart | 116 ++++++++++++++++++ catalyst_voices/lib/widgets/widgets.dart | 1 + .../catalyst_voices_localizations.dart | 18 +++ .../catalyst_voices_localizations_en.dart | 9 ++ .../catalyst_voices_localizations_es.dart | 9 ++ .../lib/l10n/intl_en.arb | 12 ++ .../src/{ => auth}/authentication_status.dart | 0 .../lib/src/auth/password_strength.dart | 27 ++++ .../lib/src/catalyst_voices_models.dart | 3 +- .../catalyst_voices_models/pubspec.yaml | 4 +- .../test/auth/password_strength_test.dart | 55 +++++++++ .../examples/voices_indicators_example.dart | 52 +++++--- melos.yaml | 1 + 13 files changed, 285 insertions(+), 22 deletions(-) create mode 100644 catalyst_voices/lib/widgets/indicators/voices_password_strength_indicator.dart rename catalyst_voices/packages/catalyst_voices_models/lib/src/{ => auth}/authentication_status.dart (100%) create mode 100644 catalyst_voices/packages/catalyst_voices_models/lib/src/auth/password_strength.dart create mode 100644 catalyst_voices/packages/catalyst_voices_models/test/auth/password_strength_test.dart diff --git a/catalyst_voices/lib/widgets/indicators/voices_password_strength_indicator.dart b/catalyst_voices/lib/widgets/indicators/voices_password_strength_indicator.dart new file mode 100644 index 0000000000..68333b325d --- /dev/null +++ b/catalyst_voices/lib/widgets/indicators/voices_password_strength_indicator.dart @@ -0,0 +1,116 @@ +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +/// An indicator for a [PasswordStrength]. +/// +/// Fills in all the available horizontal space, +/// use a [SizedBox] to limit it's width. +final class VoicesPasswordStrengthIndicator extends StatelessWidget { + final PasswordStrength passwordStrength; + + const VoicesPasswordStrengthIndicator({ + super.key, + required this.passwordStrength, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Label(passwordStrength: passwordStrength), + const SizedBox(height: 16), + _Indicator(passwordStrength: passwordStrength), + ], + ); + } +} + +class _Label extends StatelessWidget { + final PasswordStrength passwordStrength; + + const _Label({required this.passwordStrength}); + + @override + Widget build(BuildContext context) { + return Text( + switch (passwordStrength) { + PasswordStrength.weak => context.l10n.weakPasswordStrength, + PasswordStrength.normal => context.l10n.normalPasswordStrength, + PasswordStrength.strong => context.l10n.goodPasswordStrength, + }, + style: Theme.of(context).textTheme.bodySmall, + ); + } +} + +class _Indicator extends StatelessWidget { + static const double _backgroundTrackHeight = 4; + static const double _foregroundTrackHeight = 6; + static const double _tracksGap = 8; + + final PasswordStrength passwordStrength; + + const _Indicator({required this.passwordStrength}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: _foregroundTrackHeight, + child: LayoutBuilder( + builder: (context, constraints) { + final totalWidthOfAllGaps = + (PasswordStrength.values.length - 1) * _tracksGap; + final availableWidth = constraints.maxWidth - totalWidthOfAllGaps; + final trackWidth = availableWidth / PasswordStrength.values.length; + + return Stack( + children: [ + Positioned.fill( + top: 1, + child: Container( + height: _backgroundTrackHeight, + decoration: BoxDecoration( + color: Theme.of(context).colors.onSurfaceSecondary08, + borderRadius: BorderRadius.circular(_backgroundTrackHeight), + ), + ), + ), + for (final strength in PasswordStrength.values) + if (passwordStrength.index >= strength.index) + Positioned( + left: strength.index * (trackWidth + _tracksGap), + width: trackWidth, + child: _Track(passwordStrength: strength), + ), + ], + ); + }, + ), + ); + } +} + +class _Track extends StatelessWidget { + final PasswordStrength passwordStrength; + + const _Track({required this.passwordStrength}); + + @override + Widget build(BuildContext context) { + return Container( + height: _Indicator._foregroundTrackHeight, + decoration: BoxDecoration( + color: switch (passwordStrength) { + PasswordStrength.weak => Theme.of(context).colorScheme.error, + PasswordStrength.normal => Theme.of(context).colors.warning, + PasswordStrength.strong => Theme.of(context).colors.success, + }, + borderRadius: BorderRadius.circular(_Indicator._foregroundTrackHeight), + ), + ); + } +} diff --git a/catalyst_voices/lib/widgets/widgets.dart b/catalyst_voices/lib/widgets/widgets.dart index 0b9e550ea8..ff32170b6e 100644 --- a/catalyst_voices/lib/widgets/widgets.dart +++ b/catalyst_voices/lib/widgets/widgets.dart @@ -34,6 +34,7 @@ export 'indicators/process_progress_indicator.dart'; export 'indicators/voices_circular_progress_indicator.dart'; export 'indicators/voices_linear_progress_indicator.dart'; export 'indicators/voices_no_internet_connection_banner.dart'; +export 'indicators/voices_password_strength_indicator.dart'; export 'indicators/voices_status_indicator.dart'; export 'menu/voices_list_tile.dart'; export 'menu/voices_menu.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index 2ae03bd9b8..1105d7c665 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -645,6 +645,24 @@ abstract class VoicesLocalizations { /// In en, this message translates to: /// **'Your internet is playing hide and seek. Check your internet connection, or try again in a moment.'** String get noConnectionBannerDescription; + + /// Describes a password that is weak + /// + /// In en, this message translates to: + /// **'Weak password strength'** + String get weakPasswordStrength; + + /// Describes a password that has medium strength. + /// + /// In en, this message translates to: + /// **'Normal password strength'** + String get normalPasswordStrength; + + /// Describes a password that is strong. + /// + /// In en, this message translates to: + /// **'Good password strength'** + String get goodPasswordStrength; } class _VoicesLocalizationsDelegate extends LocalizationsDelegate { diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index 967640ee62..b4ae93ae36 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -336,4 +336,13 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get noConnectionBannerDescription => 'Your internet is playing hide and seek. Check your internet connection, or try again in a moment.'; + + @override + String get weakPasswordStrength => 'Weak password strength'; + + @override + String get normalPasswordStrength => 'Normal password strength'; + + @override + String get goodPasswordStrength => 'Good password strength'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index 33e8773d20..734ef69842 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -336,4 +336,13 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get noConnectionBannerDescription => 'Your internet is playing hide and seek. Check your internet connection, or try again in a moment.'; + + @override + String get weakPasswordStrength => 'Weak password strength'; + + @override + String get normalPasswordStrength => 'Normal password strength'; + + @override + String get goodPasswordStrength => 'Good password strength'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb index 215e697aaf..65c3750b5b 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -413,5 +413,17 @@ "noConnectionBannerDescription": "Your internet is playing hide and seek. Check your internet connection, or try again in a moment.", "@noConnectionBannerDescription": { "description": "Text shown in the No Internet Connection Banner widget for the description below the title." + }, + "weakPasswordStrength": "Weak password strength", + "@weakPasswordStrength": { + "description": "Describes a password that is weak" + }, + "normalPasswordStrength": "Normal password strength", + "@normalPasswordStrength": { + "description": "Describes a password that has medium strength." + }, + "goodPasswordStrength": "Good password strength", + "@goodPasswordStrength": { + "description": "Describes a password that is strong." } } \ No newline at end of file diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/authentication_status.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/auth/authentication_status.dart similarity index 100% rename from catalyst_voices/packages/catalyst_voices_models/lib/src/authentication_status.dart rename to catalyst_voices/packages/catalyst_voices_models/lib/src/auth/authentication_status.dart diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/auth/password_strength.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/auth/password_strength.dart new file mode 100644 index 0000000000..5f5c1205b3 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/auth/password_strength.dart @@ -0,0 +1,27 @@ +import 'package:password_strength/password_strength.dart' as ps; + +/// Describes strength of a password. +/// +/// The enum values must be sorted from the weakest to the strongest. +enum PasswordStrength { + /// A weak password. Simple, already exposed, commonly used, etc. + weak, + + /// A medium password, not complex. + normal, + + /// A complex password with characters from different groups. + strong; + + /// The minimum length of accepted password. + static const int minimumPasswordLength = 8; + + factory PasswordStrength.calculate(String text) { + if (text.length < minimumPasswordLength) return PasswordStrength.weak; + + final strength = ps.estimatePasswordStrength(text); + if (strength <= 0.33) return PasswordStrength.weak; + if (strength <= 0.66) return PasswordStrength.normal; + return PasswordStrength.strong; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart index 26b783cd49..072d1267fe 100644 --- a/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -1,6 +1,7 @@ library catalyst_voices_models; -export 'authentication_status.dart'; +export 'auth/authentication_status.dart'; +export 'auth/password_strength.dart'; export 'errors/errors.dart'; export 'proposal/funded_proposal.dart'; export 'proposal/pending_proposal.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_models/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_models/pubspec.yaml index df456f50f1..a25c85724b 100644 --- a/catalyst_voices/packages/catalyst_voices_models/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_models/pubspec.yaml @@ -11,7 +11,9 @@ dependencies: equatable: ^2.0.5 flutter_quill: ^10.5.13 meta: ^1.10.0 + password_strength: ^0.2.0 dev_dependencies: catalyst_analysis: ^2.0.0 - test: ^1.24.9 + flutter_test: + sdk: flutter diff --git a/catalyst_voices/packages/catalyst_voices_models/test/auth/password_strength_test.dart b/catalyst_voices/packages/catalyst_voices_models/test/auth/password_strength_test.dart new file mode 100644 index 0000000000..108ef53ca9 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/test/auth/password_strength_test.dart @@ -0,0 +1,55 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group(PasswordStrength, () { + test('weak password - too short', () { + expect( + PasswordStrength.calculate('123456'), + equals(PasswordStrength.weak), + ); + + expect( + PasswordStrength.calculate('Ab1!@_'), + equals(PasswordStrength.weak), + ); + }); + + test('weak password - too popular', () { + expect( + PasswordStrength.calculate('password'), + equals(PasswordStrength.weak), + ); + }); + + test('weak password - too simple', () { + expect( + /* cSpell:disable */ + PasswordStrength.calculate('simplepw'), + /* cSpell:enable */ + equals(PasswordStrength.weak), + ); + }); + + test('normal password', () { + expect( + PasswordStrength.calculate('Passwd12'), + equals(PasswordStrength.normal), + ); + }); + + test('strong password', () { + expect( + PasswordStrength.calculate('Passwd!@'), + equals(PasswordStrength.strong), + ); + }); + + test('strong password', () { + expect( + PasswordStrength.calculate('4Gf;Rd04WP,RxgBl)n5&RlG'), + equals(PasswordStrength.strong), + ); + }); + }); +} diff --git a/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart index 1f8e778b76..60c4b6c294 100644 --- a/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart +++ b/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart @@ -1,5 +1,6 @@ import 'package:catalyst_voices/widgets/common/affix_decorator.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; import 'package:flutter/material.dart'; @@ -16,9 +17,9 @@ class VoicesIndicatorsExample extends StatelessWidget { appBar: AppBar(title: const Text('Voices Indicators')), body: ListView( padding: const EdgeInsets.symmetric(horizontal: 42, vertical: 24), - children: const [ - Text('Status Indicator'), - Row( + children: [ + const Text('Status Indicator'), + const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -30,7 +31,7 @@ class VoicesIndicatorsExample extends StatelessWidget { ), title: Text('Your QR code verified successfully'), body: Text( - 'You can now use your QR-code 
to login into Catalyst.', + 'You can now use your QR-code to login into Catalyst.', ), type: VoicesStatusIndicatorType.success, ), @@ -45,24 +46,24 @@ class VoicesIndicatorsExample extends StatelessWidget { title: Text('Upload failed or QR code not recognized!'), body: Text( 'Are you sure your upload didn’t get interrupted or that ' - 'you provided 
a Catalyst QR code? ' - '

Please try again.', + 'you provided a Catalyst QR code? ' + 'Please try again.', ), type: VoicesStatusIndicatorType.error, ), ), ], ), - Text('Process Stepper Indicator'), - _Steps(), - Text('Linear - Indeterminate'), - VoicesLinearProgressIndicator(), - VoicesLinearProgressIndicator(showTrack: false), - Text('Linear - Fixed'), - VoicesLinearProgressIndicator(value: 0.25), - VoicesLinearProgressIndicator(value: 0.25, showTrack: false), - Text('Circular - Indeterminate'), - Row( + const Text('Process Stepper Indicator'), + const _Steps(), + const Text('Linear - Indeterminate'), + const VoicesLinearProgressIndicator(), + const VoicesLinearProgressIndicator(showTrack: false), + const Text('Linear - Fixed'), + const VoicesLinearProgressIndicator(value: 0.25), + const VoicesLinearProgressIndicator(value: 0.25, showTrack: false), + const Text('Circular - Indeterminate'), + const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ VoicesCircularProgressIndicator(), @@ -70,8 +71,8 @@ class VoicesIndicatorsExample extends StatelessWidget { VoicesCircularProgressIndicator(showTrack: false), ], ), - Text('Circular - Fixed'), - Row( + const Text('Circular - Fixed'), + const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ VoicesCircularProgressIndicator(value: 0.75), @@ -79,8 +80,19 @@ class VoicesIndicatorsExample extends StatelessWidget { VoicesCircularProgressIndicator(value: 0.75, showTrack: false), ], ), - Text('No Internet Connection Banner'), - NoInternetConnectionBanner(), + const Text('No Internet Connection Banner'), + const NoInternetConnectionBanner(), + const Text('Password strength indicator'), + for (final passwordStrength in PasswordStrength.values) + Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: VoicesPasswordStrengthIndicator( + passwordStrength: passwordStrength, + ), + ), + ), ].separatedByIndexed( (index, value) { return switch (value.runtimeType) { diff --git a/melos.yaml b/melos.yaml index 7378b9e202..2e38ced9f7 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,6 +32,7 @@ command: logging: ^1.2.0 meta: ^1.10.0 result_type: ^0.2.0 + password_strength: ^0.2.0 plugin_platform_interface: ^2.1.7 bech32: ^0.2.2 bip32_ed25519: ^0.6.0