From 656749b76417f5d909cf34b17691c0bd84ae8b3f Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Tue, 17 Dec 2024 11:21:56 +0100 Subject: [PATCH] fix: support caching of new Metadata_metadata_at_version method, fix of cache miss (#2864) --- package.json | 13 +- pnpm-lock.yaml | 362 ++++++++++++++---- .../entities/network/lib/network-utils.ts | 2 +- .../model/__tests__/network-model.test.ts | 30 +- .../entities/network/model/network-model.ts | 127 +++--- .../network/__tests__/metadataService.test.ts | 26 +- src/renderer/shared/api/network/lib/types.ts | 8 +- .../api/network/provider/CachedProvider.ts | 76 +++- .../api/network/service/metadataService.ts | 31 +- .../api/network/service/networkService.ts | 6 +- .../shared/api/storage/service/dexie.ts | 8 + src/renderer/shared/core/types/metadata.ts | 3 +- .../shared/effector/helpers/createBuffer.ts | 52 +++ src/renderer/shared/effector/index.ts | 1 + src/renderer/shared/lib/utils/substrate.ts | 6 +- 15 files changed, 544 insertions(+), 207 deletions(-) create mode 100644 src/renderer/shared/effector/helpers/createBuffer.ts diff --git a/package.json b/package.json index 7619c948ea..88f10f25fa 100644 --- a/package.json +++ b/package.json @@ -69,11 +69,11 @@ "@apollo/client": "3.11.8", "@ariakit/react": "0.4.14", "@headlessui/react": "^1.7.17", - "@polkadot/api": "14.3.1", + "@polkadot/api": "15.0.2", "@polkadot/keyring": "13.2.3", "@polkadot/react-identicon": "3.11.3", - "@polkadot/rpc-provider": "14.3.1", - "@polkadot/types": "14.3.1", + "@polkadot/rpc-provider": "15.0.2", + "@polkadot/types": "15.0.2", "@polkadot/util": "13.2.3", "@polkadot/util-crypto": "13.2.3", "@radix-ui/react-accordion": "1.2.1", @@ -87,9 +87,9 @@ "@radix-ui/react-tooltip": "1.1.3", "@react-spring/web": "9.7.5", "@remote-ui/rpc": "^1.4.4", - "@substrate/connect": "1.3.1", - "@substrate/txwrapper-orml": "7.5.2", - "@substrate/txwrapper-polkadot": "7.5.2", + "@substrate/connect": "2.1.1", + "@substrate/txwrapper-orml": "7.5.3", + "@substrate/txwrapper-polkadot": "7.5.3", "@walletconnect/types": "2.17.2", "@walletconnect/universal-provider": "^2.17.2", "@walletconnect/utils": "2.17.2", @@ -119,6 +119,7 @@ "lodash": "4.17.21", "lodash-es": "4.17.21", "lottie-react": "2.4.0", + "mitt": "3.0.1", "parity-scale-codec": "^0.6.1", "patronum": "2.2.0", "qr-code-styling": "1.6.0-rc.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c0773a2e1..e8bd0ee51c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.7.17 version: 1.7.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@polkadot/api': - specifier: 14.3.1 - version: 14.3.1 + specifier: 15.0.2 + version: 15.0.2 '@polkadot/keyring': specifier: 13.2.3 version: 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) @@ -27,11 +27,11 @@ importers: specifier: 3.11.3 version: 3.11.3(@polkadot/keyring@13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3))(@polkadot/networks@13.2.3)(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@18.2.0)(react@18.3.1) '@polkadot/rpc-provider': - specifier: 14.3.1 - version: 14.3.1 + specifier: 15.0.2 + version: 15.0.2 '@polkadot/types': - specifier: 14.3.1 - version: 14.3.1 + specifier: 15.0.2 + version: 15.0.2 '@polkadot/util': specifier: 13.2.3 version: 13.2.3 @@ -72,14 +72,14 @@ importers: specifier: ^1.4.4 version: 1.4.4 '@substrate/connect': - specifier: 1.3.1 - version: 1.3.1 + specifier: 2.1.1 + version: 2.1.1 '@substrate/txwrapper-orml': - specifier: 7.5.2 - version: 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + specifier: 7.5.3 + version: 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) '@substrate/txwrapper-polkadot': - specifier: 7.5.2 - version: 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + specifier: 7.5.3 + version: 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) '@walletconnect/types': specifier: 2.17.2 version: 2.17.2 @@ -167,6 +167,9 @@ importers: lottie-react: specifier: 2.4.0 version: 2.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + mitt: + specifier: 3.0.1 + version: 3.0.1 parity-scale-codec: specifier: ^0.6.1 version: 0.6.1 @@ -2007,18 +2010,34 @@ packages: resolution: {integrity: sha512-PE6DW+8kRhbnGKn7qCF7yM6eEt/kqrY8bh1i0RZcPY9QgwXW4bZZrtMK4WssX6Z70NTEoOW6xHYIjc7gFZuz8g==} engines: {node: '>=18'} + '@polkadot/api-augment@15.0.2': + resolution: {integrity: sha512-7qtfTihLKS7cT2kEsd8Y1+MJ+2n4Sl0y9BHuPhdNfKcDbGwCxIB7JzXNujww4Is4bF7w1lXcM2U0E/XwJi1BbQ==} + engines: {node: '>=18'} + '@polkadot/api-base@14.3.1': resolution: {integrity: sha512-GZT6rTpT3HYZ/C3rLPjoX3rX3DOxNG/zgts+jKjNrCumAeZkVq5JErKIX8/3f2TVaE2Kbqniy3d1TH/AL4HBPA==} engines: {node: '>=18'} + '@polkadot/api-base@15.0.2': + resolution: {integrity: sha512-5++EjpuCxzmrL2JJj511RrPK+IovuIQa8DJhyOp62VDMXPkqS/O6Df5wjYzQh/ftT1ZysftXqJAUA/LSUsH+2g==} + engines: {node: '>=18'} + '@polkadot/api-derive@14.3.1': resolution: {integrity: sha512-PhqUEJCY54vXtIaoYqGUtJY06wHd/K0cBmBz9yCLxp8UZkLoGWhfJRTruI25Jnucf9awS5cZKYqbsoDrL09Oqg==} engines: {node: '>=18'} + '@polkadot/api-derive@15.0.2': + resolution: {integrity: sha512-nD8hXZxEv2/wuhjMmVP09lTAYd8inNgrLpLGUR+7hBQeVEQQTdNatkqMKpNIOk2MO46mtUK35NepvDBK9DWZzA==} + engines: {node: '>=18'} + '@polkadot/api@14.3.1': resolution: {integrity: sha512-ZBKSXEVJa1S1bnmpnA7KT/fX3sJDIJOdVD9Hp3X+G73yvXzuK5k1Mn5z9bD/AcMs/HAGcbuYU+b9+b9IByH9YQ==} engines: {node: '>=18'} + '@polkadot/api@15.0.2': + resolution: {integrity: sha512-CA8Pq2Gsz2MJvpkpIVNzaBs2eJGCr0sEodAb0vTOZW/ZlIDHcBWyWq3KXE+lBMWVzYBEC4Vz6MafNgq6Bsshlw==} + engines: {node: '>=18'} + '@polkadot/keyring@13.2.3': resolution: {integrity: sha512-pgTo6DXNXub0wGD+MnVHYhKxf80Jl+QMOCb818ioGdXz++Uw4mTueFAwtB+N7TGo0HafhChUiNJDxFdlDkcAng==} engines: {node: '>=18'} @@ -2045,38 +2064,74 @@ packages: resolution: {integrity: sha512-Z8Hp8fFHwFCiTX0bBCDqCZ4U26wLIJl1NRSjJTsAr+SS68pYZBDGCwhKztpKGqndk1W1akRUaxrkGqYdIFmspQ==} engines: {node: '>=18'} + '@polkadot/rpc-augment@15.0.2': + resolution: {integrity: sha512-of88GdzsOs15HP+Gowh4G/iKKFkCNIeF0Wes8LiONfn+j2TmWt8blbyGhrIZZeApzbFUDFjnxUPGT40uWJcMpw==} + engines: {node: '>=18'} + '@polkadot/rpc-core@14.3.1': resolution: {integrity: sha512-FV2NPhFwFxmX8LqibDcGc6IKTBqmvwr7xwF2OA60Br4cX+AQzMSVpFlfQcETll+0M+LnRhqGKGkP0EQWXaSowA==} engines: {node: '>=18'} + '@polkadot/rpc-core@15.0.2': + resolution: {integrity: sha512-KOUnfXOAFCN0N23sEbS+FzXhX88JCvJst/nKzO9+q67NgYBEqgcaoG4tqt/VVedgkNi0kA7WAA3wyjt5UYMnrg==} + engines: {node: '>=18'} + '@polkadot/rpc-provider@14.3.1': resolution: {integrity: sha512-NF/Z/7lzT+jp5LZzC49g+YIjRzXVI0hFag3+B+4zh6E/kKADdF59EHj2Im4LDhRGOnEO9AE4H6/UjNEbZ94JtA==} engines: {node: '>=18'} + '@polkadot/rpc-provider@15.0.2': + resolution: {integrity: sha512-BwLP8gNskzqtQ2kMk+EX6WK4d9TU0XJ/nJg0TKC2dX5sSTpTF2JQIYp1wuOik4rKNXIU/1hKaDSSylMJj7AHeQ==} + engines: {node: '>=18'} + '@polkadot/types-augment@14.3.1': resolution: {integrity: sha512-SC4M6TBlgCglNz+gRbvfoVRDz0Vyeev6v0HeAdw0H6ayEW4BXUdo5bFr0092bdS5uTrEPgiSyUry5TJs2KoXig==} engines: {node: '>=18'} + '@polkadot/types-augment@15.0.2': + resolution: {integrity: sha512-UiFJVEYML30+V9GdFAHPbA3s4MVQTL1CevsZMnX0+ApvlgEHJMZnVFfYF7jL2bl9BcUYM/zoxEAhj2MpqFFfxw==} + engines: {node: '>=18'} + '@polkadot/types-codec@14.3.1': resolution: {integrity: sha512-3y3RBGd+8ebscGbNUOjqUjnRE7hgicgid5LtofHK3O1EDcJQJnYBDkJ7fOAi96CDgHsg+f2FWWkBWEPgpOQoMQ==} engines: {node: '>=18'} + '@polkadot/types-codec@15.0.2': + resolution: {integrity: sha512-44Q40p1rl0t7Bl1QUamewqXNVPway9xgqByyifv6ODSGhtt+lFoarb3U4JzqRUuuK0PP57ePB0L8q81Totxeew==} + engines: {node: '>=18'} + '@polkadot/types-create@14.3.1': resolution: {integrity: sha512-F4EBvF3Zvym0xrkAA5Yz01IAVMepMV3w2Dwd0C9IygEAQ5sYLLPHmf72/aXn+Ag+bSyT2wlJHpDc+nEBXNQ3Gw==} engines: {node: '>=18'} + '@polkadot/types-create@15.0.2': + resolution: {integrity: sha512-YhpcqbH3oI87PkgrV6Fez9jWDqFIep0KcS1YWQcwc9gsBNnuour80t2AAK41/tqAYwOZi6tpJwIevnEhVkxFYA==} + engines: {node: '>=18'} + '@polkadot/types-known@14.3.1': resolution: {integrity: sha512-58b3Yc7+sxwNjs8axmrA9OCgnxmEKIq7XCH2VxSgLqTeqbohVtxwUSCW/l8NPrq1nxzj4J2sopu0PPg8/++q4g==} engines: {node: '>=18'} + '@polkadot/types-known@15.0.2': + resolution: {integrity: sha512-SyBo4xBoesHYiEfdW/nOgaftKgM7+puBWqQXq1Euz0MM5LDLjxBw22Srgk9ulGM6l9MsekIwCyX8geJ6/6J3Cg==} + engines: {node: '>=18'} + '@polkadot/types-support@14.3.1': resolution: {integrity: sha512-MfVe4iIOJIfBr+gj8Lu8gwIvhnO6gDbG5LeaKAjY6vS6Oh0y5Ztr8NdMIl8ccSpoyt3LqIXjfApeGzHiLzr6bw==} engines: {node: '>=18'} + '@polkadot/types-support@15.0.2': + resolution: {integrity: sha512-I7Im/4K2/XDZ1LfeQeeNyvIkfvozIPQs8K1/nsICDOmBbvIsjxSeKWHOcFDMJ8AlPEer6bqNQavOyZA/pg9R/Q==} + engines: {node: '>=18'} + '@polkadot/types@14.3.1': resolution: {integrity: sha512-O748XgCLDQYxS5nQ6TJSqW88oC4QNIoNVlWZC2Qq4SmEXuSzaNHQwSVtdyPRJCCc4Oi1DCQvGui4O+EukUl7HA==} engines: {node: '>=18'} + '@polkadot/types@15.0.2': + resolution: {integrity: sha512-6gZBnuXU58hQAXWI2daED2OaQwFMxbQdkE8HVGMovMobEf0PxPfIqf+GdnVmWbe09EU9mv2gCWBcdnvHSRBlQg==} + engines: {node: '>=18'} + '@polkadot/ui-settings@3.11.3': resolution: {integrity: sha512-VGtv5pGQM9iFWZ9kpU9TD0auDUb01mTp/DACTgG79mo9icK2XK8KUdkMMh70HuhnS5Dnqsgn7nhYuYqVOGAxeQ==} engines: {node: '>=18'} @@ -2899,24 +2954,21 @@ packages: peerDependencies: storybook: ^8.3.6 - '@substrate/connect-extension-protocol@2.0.0': - resolution: {integrity: sha512-nKu8pDrE3LNCEgJjZe1iGXzaD6OSIDD4Xzz/yo4KO9mQ6LBvf49BVrt4qxBFGL6++NneLiWUZGoh+VSd4PyVIg==} - '@substrate/connect-extension-protocol@2.2.1': resolution: {integrity: sha512-GoafTgm/Jey9E4Xlj4Z5ZBt/H4drH2CNq8VrAro80rtoznrXnFDNVivLQzZN0Xaj2g8YXSn9pC9Oc9IovYZJXw==} - '@substrate/connect-known-chains@1.2.0': - resolution: {integrity: sha512-BgcTHKteSAcEQs5ySNTYOO6ODQHVHwPgDrjYQhL0r8ZygY4cyXa5e2O//3tXNJiDopFHdqO8FBAy2Gbht0i0PA==} - '@substrate/connect-known-chains@1.7.0': resolution: {integrity: sha512-Qf+alxEPmNycUyrPkXWrlFA97punnBCGxSWqiLG1CNu+jQoFYqi8x7gZYfqmdUHDY4nG1F84KHPPk7Zy4ngSfg==} + '@substrate/connect-known-chains@1.8.1': + resolution: {integrity: sha512-WkqXvuLWL1g136nxAoGkclybsVpo8claHeBl/8iA99ECAk4pWLtoCh8PoYrFEE1HfY4rk+A0krPybDlcDa/G4g==} + '@substrate/connect@0.8.11': resolution: {integrity: sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw==} deprecated: versions below 1.x are no longer maintained - '@substrate/connect@1.3.1': - resolution: {integrity: sha512-jIvudD/wjATjsoiQvAQxHT6WV4eZUEc/wtuUVix7SPLRLSTEI9El8K7TGdy6rsWoqv0rljLojyRH+exZzwkIkg==} + '@substrate/connect@2.1.1': + resolution: {integrity: sha512-25/F2A6OCs/0IpMPxRPP7RycT6/vDU6YL4rS2Wfqgcs3Ru7ATxGxS9jAVVEYuIkvdZ4baVcpC3a4KgH65mNeNw==} '@substrate/discovery@0.2.1': resolution: {integrity: sha512-SRWlBK65Qy6WvHG2V2vjctJaz07Td2HZTw58z59bZOcr+I10q7OxqFKYPIvQwz/VWrWEGdHQ57jt7EwS4T6mDQ==} @@ -2926,23 +2978,23 @@ packages: peerDependencies: smoldot: 2.x - '@substrate/smoldot-discovery@1.2.0': - resolution: {integrity: sha512-Vs+9Y0JuvoAUtL35uKtXD/Qnb5WDksHHBvcbjhGIdgyMZJs5KFb+2iL+M7bJ71xq6qqAGjwI8Gdmf/B9CZHL2w==} + '@substrate/smoldot-discovery@2.0.1': + resolution: {integrity: sha512-8IXrZN4X6mOb/VOYF6Sy+MWSICiZQ92475ca/8fU65f54JkbXhslF6CmIUm2pGDHb51/uNZ+ZUkc/zPHwBdI2w==} '@substrate/ss58-registry@1.51.0': resolution: {integrity: sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==} - '@substrate/txwrapper-core@7.5.2': - resolution: {integrity: sha512-QbWNA8teVYS2YfrZ5JWtl6nmX11UIS7k4ZF5ukKe/oqaCSifoNhTYkTDnN/AWImOPQ0N0QeVge1XlF1TUtfFDA==} + '@substrate/txwrapper-core@7.5.3': + resolution: {integrity: sha512-vcb9GaAY8ex330yjJoDCa2w32R2u/KUmEKsD/5DRgTbPEUF1OYiKmmuOJWcD0jHu9HZ8HWlniiV8wxxwo3PVCA==} - '@substrate/txwrapper-orml@7.5.2': - resolution: {integrity: sha512-NNnzsvcQBCx+Nd8ZVIXWxTuej2ifcLrX9jcTgFVenO3Ae80sL/9PMMRilCaJppNYHhHDfxnto+RTQjy/oxjYdQ==} + '@substrate/txwrapper-orml@7.5.3': + resolution: {integrity: sha512-2XQG3bmGTdssh7zdEcX/YlO4Mflo0fuxpUhZKAicUV2dr+x+LP1y1BCNkTy0V6AU4rAY4oBIGm7HTWpNk4XZNA==} - '@substrate/txwrapper-polkadot@7.5.2': - resolution: {integrity: sha512-ekTnEOdyuwjHj2HIjh2NK7Rqu5QadKVMsTadU/bAfAoKJEEFXy/WMnsmCahGg09eFx77MX/rPuqv6IRQxl+8VA==} + '@substrate/txwrapper-polkadot@7.5.3': + resolution: {integrity: sha512-ftjOJ0PfIBxQHckUrF1eeQwH2la5SNvhuZRCoIC5ibX77FHmYI5VpAiKlNCUOXUz8BY8ohaAu2IouL6tj5qzvQ==} - '@substrate/txwrapper-substrate@7.5.2': - resolution: {integrity: sha512-CzlTrq5WoXo7ZJoQPtqUf+w0PViPhlctMyAjuKV8RvWDQaVtW8BW7wbCKKV2WfNOQsIBaf4I7fU/q3Qk5Am/lw==} + '@substrate/txwrapper-substrate@7.5.3': + resolution: {integrity: sha512-hAZTcZzB7uGLwm62JFCmxM9M0lm+5Id1tROqdKkuukesAAt06UcTTiaopq2w/B9syWamMor63aQEFugEIK2PLQ==} '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} @@ -4800,8 +4852,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - d@1.0.1: - resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} @@ -7259,8 +7312,9 @@ packages: resolution: {integrity: sha512-dIs5KGy24fbdDhIAg0RxXpFqQp3RwL6wgSMRF9OSuphL/Uc9a4u2/SDJKPLj/zUgtOGKuHrRMrj563+IErj4Cg==} engines: {node: '>= 4.0.0'} - memoizee@0.4.15: - resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + memoizee@0.4.17: + resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} + engines: {node: '>=0.12'} memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -7507,6 +7561,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -9126,8 +9183,8 @@ packages: smoldot@2.0.26: resolution: {integrity: sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==} - smoldot@2.0.30: - resolution: {integrity: sha512-5/KaEdIlTHg4+Hs8YovrCkwI+Y9QsKtoyWkHRqz15kXMZFsePf+KdMAPpDgReINK+at6t+D+uEVrCkd+r9JpyQ==} + smoldot@2.0.34: + resolution: {integrity: sha512-mw9tCbGEhEp0koMqLL0jBEixVY1MIN/xI3pE6ZY1TuOPU+LnYy8FloODVyzkvzQPaBYrETXJdRlmA/+k6g3gow==} snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -9742,9 +9799,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type@1.2.0: - resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} - type@2.7.2: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} @@ -12234,6 +12288,20 @@ snapshots: - supports-color - utf-8-validate + '@polkadot/api-augment@15.0.2': + dependencies: + '@polkadot/api-base': 15.0.2 + '@polkadot/rpc-augment': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/types-augment': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@polkadot/api-base@14.3.1': dependencies: '@polkadot/rpc-core': 14.3.1 @@ -12246,6 +12314,18 @@ snapshots: - supports-color - utf-8-validate + '@polkadot/api-base@15.0.2': + dependencies: + '@polkadot/rpc-core': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/util': 13.2.3 + rxjs: 7.8.1 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@polkadot/api-derive@14.3.1': dependencies: '@polkadot/api': 14.3.1 @@ -12263,6 +12343,23 @@ snapshots: - supports-color - utf-8-validate + '@polkadot/api-derive@15.0.2': + dependencies: + '@polkadot/api': 15.0.2 + '@polkadot/api-augment': 15.0.2 + '@polkadot/api-base': 15.0.2 + '@polkadot/rpc-core': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/util': 13.2.3 + '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) + rxjs: 7.8.1 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@polkadot/api@14.3.1': dependencies: '@polkadot/api-augment': 14.3.1 @@ -12281,7 +12378,31 @@ snapshots: '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) eventemitter3: 5.0.1 rxjs: 7.8.1 - tslib: 2.8.0 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@polkadot/api@15.0.2': + dependencies: + '@polkadot/api-augment': 15.0.2 + '@polkadot/api-base': 15.0.2 + '@polkadot/api-derive': 15.0.2 + '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@polkadot/rpc-augment': 15.0.2 + '@polkadot/rpc-core': 15.0.2 + '@polkadot/rpc-provider': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/types-augment': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/types-create': 15.0.2 + '@polkadot/types-known': 15.0.2 + '@polkadot/util': 13.2.3 + '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) + eventemitter3: 5.0.1 + rxjs: 7.8.1 + tslib: 2.8.1 transitivePeerDependencies: - bufferutil - supports-color @@ -12329,6 +12450,18 @@ snapshots: - supports-color - utf-8-validate + '@polkadot/rpc-augment@15.0.2': + dependencies: + '@polkadot/rpc-core': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@polkadot/rpc-core@14.3.1': dependencies: '@polkadot/rpc-augment': 14.3.1 @@ -12342,6 +12475,19 @@ snapshots: - supports-color - utf-8-validate + '@polkadot/rpc-core@15.0.2': + dependencies: + '@polkadot/rpc-augment': 15.0.2 + '@polkadot/rpc-provider': 15.0.2 + '@polkadot/types': 15.0.2 + '@polkadot/util': 13.2.3 + rxjs: 7.8.1 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@polkadot/rpc-provider@14.3.1': dependencies: '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) @@ -12355,7 +12501,28 @@ snapshots: eventemitter3: 5.0.1 mock-socket: 9.3.1 nock: 13.5.5 - tslib: 2.8.0 + tslib: 2.8.1 + optionalDependencies: + '@substrate/connect': 0.8.11 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@polkadot/rpc-provider@15.0.2': + dependencies: + '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@polkadot/types': 15.0.2 + '@polkadot/types-support': 15.0.2 + '@polkadot/util': 13.2.3 + '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) + '@polkadot/x-fetch': 13.2.3 + '@polkadot/x-global': 13.2.3 + '@polkadot/x-ws': 13.2.3 + eventemitter3: 5.0.1 + mock-socket: 9.3.1 + nock: 13.5.5 + tslib: 2.8.1 optionalDependencies: '@substrate/connect': 0.8.11 transitivePeerDependencies: @@ -12370,18 +12537,37 @@ snapshots: '@polkadot/util': 13.2.3 tslib: 2.8.1 + '@polkadot/types-augment@15.0.2': + dependencies: + '@polkadot/types': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + '@polkadot/types-codec@14.3.1': dependencies: '@polkadot/util': 13.2.3 '@polkadot/x-bigint': 13.2.3 tslib: 2.8.1 + '@polkadot/types-codec@15.0.2': + dependencies: + '@polkadot/util': 13.2.3 + '@polkadot/x-bigint': 13.2.3 + tslib: 2.8.1 + '@polkadot/types-create@14.3.1': dependencies: '@polkadot/types-codec': 14.3.1 '@polkadot/util': 13.2.3 tslib: 2.8.1 + '@polkadot/types-create@15.0.2': + dependencies: + '@polkadot/types-codec': 15.0.2 + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + '@polkadot/types-known@14.3.1': dependencies: '@polkadot/networks': 13.2.3 @@ -12391,11 +12577,25 @@ snapshots: '@polkadot/util': 13.2.3 tslib: 2.8.1 + '@polkadot/types-known@15.0.2': + dependencies: + '@polkadot/networks': 13.2.3 + '@polkadot/types': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/types-create': 15.0.2 + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + '@polkadot/types-support@14.3.1': dependencies: '@polkadot/util': 13.2.3 tslib: 2.8.1 + '@polkadot/types-support@15.0.2': + dependencies: + '@polkadot/util': 13.2.3 + tslib: 2.8.1 + '@polkadot/types@14.3.1': dependencies: '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) @@ -12405,7 +12605,18 @@ snapshots: '@polkadot/util': 13.2.3 '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) rxjs: 7.8.1 - tslib: 2.8.0 + tslib: 2.8.1 + + '@polkadot/types@15.0.2': + dependencies: + '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@polkadot/types-augment': 15.0.2 + '@polkadot/types-codec': 15.0.2 + '@polkadot/types-create': 15.0.2 + '@polkadot/util': 13.2.3 + '@polkadot/util-crypto': 13.2.3(@polkadot/util@13.2.3) + rxjs: 7.8.1 + tslib: 2.8.1 '@polkadot/ui-settings@3.11.3(@polkadot/networks@13.2.3)(@polkadot/util@13.2.3)': dependencies: @@ -13461,20 +13672,17 @@ snapshots: dependencies: storybook: 8.3.6 - '@substrate/connect-extension-protocol@2.0.0': - optional: true - '@substrate/connect-extension-protocol@2.2.1': {} - '@substrate/connect-known-chains@1.2.0': + '@substrate/connect-known-chains@1.7.0': optional: true - '@substrate/connect-known-chains@1.7.0': {} + '@substrate/connect-known-chains@1.8.1': {} '@substrate/connect@0.8.11': dependencies: - '@substrate/connect-extension-protocol': 2.0.0 - '@substrate/connect-known-chains': 1.2.0 + '@substrate/connect-extension-protocol': 2.2.1 + '@substrate/connect-known-chains': 1.7.0 '@substrate/light-client-extension-helpers': 1.0.0(smoldot@2.0.26) smoldot: 2.0.26 transitivePeerDependencies: @@ -13482,12 +13690,12 @@ snapshots: - utf-8-validate optional: true - '@substrate/connect@1.3.1': + '@substrate/connect@2.1.1': dependencies: '@substrate/connect-extension-protocol': 2.2.1 - '@substrate/connect-known-chains': 1.7.0 - '@substrate/smoldot-discovery': 1.2.0 - smoldot: 2.0.30 + '@substrate/connect-known-chains': 1.8.1 + '@substrate/smoldot-discovery': 2.0.1 + smoldot: 2.0.34 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -13500,23 +13708,23 @@ snapshots: '@polkadot-api/json-rpc-provider-proxy': 0.1.0 '@polkadot-api/observable-client': 0.3.2(@polkadot-api/substrate-client@0.1.4)(rxjs@7.8.1) '@polkadot-api/substrate-client': 0.1.4 - '@substrate/connect-extension-protocol': 2.0.0 - '@substrate/connect-known-chains': 1.2.0 + '@substrate/connect-extension-protocol': 2.2.1 + '@substrate/connect-known-chains': 1.7.0 rxjs: 7.8.1 smoldot: 2.0.26 optional: true - '@substrate/smoldot-discovery@1.2.0': + '@substrate/smoldot-discovery@2.0.1': dependencies: '@substrate/discovery': 0.2.1 '@substrate/ss58-registry@1.51.0': {} - '@substrate/txwrapper-core@7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': + '@substrate/txwrapper-core@7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': dependencies: '@polkadot/api': 14.3.1 '@polkadot/keyring': 13.2.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) - memoizee: 0.4.15 + memoizee: 0.4.17 transitivePeerDependencies: - '@polkadot/util' - '@polkadot/util-crypto' @@ -13524,9 +13732,9 @@ snapshots: - supports-color - utf-8-validate - '@substrate/txwrapper-orml@7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': + '@substrate/txwrapper-orml@7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': dependencies: - '@substrate/txwrapper-core': 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@substrate/txwrapper-core': 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) transitivePeerDependencies: - '@polkadot/util' - '@polkadot/util-crypto' @@ -13534,10 +13742,10 @@ snapshots: - supports-color - utf-8-validate - '@substrate/txwrapper-polkadot@7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': + '@substrate/txwrapper-polkadot@7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': dependencies: - '@substrate/txwrapper-core': 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) - '@substrate/txwrapper-substrate': 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@substrate/txwrapper-core': 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@substrate/txwrapper-substrate': 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) transitivePeerDependencies: - '@polkadot/util' - '@polkadot/util-crypto' @@ -13545,9 +13753,9 @@ snapshots: - supports-color - utf-8-validate - '@substrate/txwrapper-substrate@7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': + '@substrate/txwrapper-substrate@7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3)': dependencies: - '@substrate/txwrapper-core': 7.5.2(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) + '@substrate/txwrapper-core': 7.5.3(@polkadot/util-crypto@13.2.3(@polkadot/util@13.2.3))(@polkadot/util@13.2.3) transitivePeerDependencies: - '@polkadot/util' - '@polkadot/util-crypto' @@ -15975,10 +16183,10 @@ snapshots: csstype@3.1.3: {} - d@1.0.1: + d@1.0.2: dependencies: es5-ext: 0.10.64 - type: 1.2.0 + type: 2.7.2 dargs@7.0.0: {} @@ -16624,18 +16832,18 @@ snapshots: es6-iterator@2.0.3: dependencies: - d: 1.0.1 + d: 1.0.2 es5-ext: 0.10.64 es6-symbol: 3.1.3 es6-symbol@3.1.3: dependencies: - d: 1.0.1 + d: 1.0.2 ext: 1.7.0 es6-weak-map@2.0.3: dependencies: - d: 1.0.1 + d: 1.0.2 es5-ext: 0.10.64 es6-iterator: 2.0.3 es6-symbol: 3.1.3 @@ -16948,7 +17156,7 @@ snapshots: esniff@2.0.1: dependencies: - d: 1.0.1 + d: 1.0.2 es5-ext: 0.10.64 event-emitter: 0.3.5 type: 2.7.2 @@ -16989,7 +17197,7 @@ snapshots: event-emitter@0.3.5: dependencies: - d: 1.0.1 + d: 1.0.2 es5-ext: 0.10.64 eventemitter3@4.0.7: {} @@ -19184,9 +19392,9 @@ snapshots: tree-dump: 1.0.2(tslib@2.7.0) tslib: 2.7.0 - memoizee@0.4.15: + memoizee@0.4.17: dependencies: - d: 1.0.1 + d: 1.0.2 es5-ext: 0.10.64 es6-weak-map: 2.0.3 event-emitter: 0.3.5 @@ -19531,6 +19739,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mitt@3.0.1: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -21223,7 +21433,7 @@ snapshots: - utf-8-validate optional: true - smoldot@2.0.30: + smoldot@2.0.34: dependencies: ws: 8.18.0 transitivePeerDependencies: @@ -21901,8 +22111,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type@1.2.0: {} - type@2.7.2: {} typed-array-buffer@1.0.0: diff --git a/src/renderer/entities/network/lib/network-utils.ts b/src/renderer/entities/network/lib/network-utils.ts index 456093b145..d9199d97c8 100644 --- a/src/renderer/entities/network/lib/network-utils.ts +++ b/src/renderer/entities/network/lib/network-utils.ts @@ -93,7 +93,7 @@ function isAutoBalanceConnection(connection: Connection): boolean { function getNewestMetadata(metadata: ChainMetadata[]): Record { return metadata.reduce>( (acc, data) => { - if (data.version >= (acc[data.chainId]?.version || -1)) { + if (data.runtimeVersion >= (acc[data.chainId]?.runtimeVersion || -1)) { acc[data.chainId] = data; } diff --git a/src/renderer/entities/network/model/__tests__/network-model.test.ts b/src/renderer/entities/network/model/__tests__/network-model.test.ts index 8e90ecf68a..7b8239b8c3 100644 --- a/src/renderer/entities/network/model/__tests__/network-model.test.ts +++ b/src/renderer/entities/network/model/__tests__/network-model.test.ts @@ -30,7 +30,8 @@ describe('entities/network/model/network-model', () => { const mockMetadata: ChainMetadata = { id: 1, - version: 1, + runtimeVersion: 1, + metadataVersion: 15, chainId: '0x01', metadata: '0x123', }; @@ -82,23 +83,21 @@ describe('entities/network/model/network-model', () => { connections: [mockConnection], metadata: [mockMetadata], }); - + const provider = { isConnected: true, onMetadataReceived: () => {} } as unknown as ProviderWithMetadata; const scope = fork(); - const spyCreateProvider = jest - .spyOn(networkService, 'createProvider') - .mockReturnValue({ isConnected: true } as ProviderWithMetadata); + const spyCreateProvider = jest.spyOn(networkService, 'createProvider').mockReturnValue(provider); await allSettled(networkModel.events.networkStarted, { scope }); expect(spyCreateProvider).toHaveBeenCalledWith( mockChainMap['0x01'].chainId, ProviderType.WEB_SOCKET, - { metadata: mockMetadata.metadata, nodes: ['http://localhost:8080'] }, + { metadata: mockMetadata, nodes: ['http://localhost:8080'] }, { onConnected: expect.any(Function), onDisconnected: expect.any(Function), onError: expect.any(Function) }, ); expect(scope.getState(networkModel._test.$providers)).toEqual({ - '0x01': { isConnected: true }, + '0x01': provider, }); }); @@ -114,13 +113,14 @@ describe('entities/network/model/network-model', () => { ], metadata: [mockMetadata], }); - - const scope = fork(); - const connectMock = jest.fn(); - const spyCreateProvider = jest - .spyOn(networkService, 'createProvider') - .mockReturnValue({ connect: connectMock, isConnected: true } as unknown as ProviderWithMetadata); + const provider = { + connect: connectMock, + isConnected: true, + onMetadataReceived: () => {}, + } as unknown as ProviderWithMetadata; + const spyCreateProvider = jest.spyOn(networkService, 'createProvider').mockReturnValue(provider); + const scope = fork(); await allSettled(networkModel.events.networkStarted, { scope }); @@ -128,11 +128,11 @@ describe('entities/network/model/network-model', () => { expect(spyCreateProvider).toHaveBeenCalledWith( mockChainMap['0x01'].chainId, ProviderType.LIGHT_CLIENT, - { metadata: mockMetadata.metadata, nodes: [''] }, + { metadata: mockMetadata, nodes: [''] }, { onConnected: expect.any(Function), onDisconnected: expect.any(Function), onError: expect.any(Function) }, ); expect(scope.getState(networkModel._test.$providers)).toEqual({ - '0x01': { connect: connectMock, isConnected: true }, + '0x01': provider, }); }); }); diff --git a/src/renderer/entities/network/model/network-model.ts b/src/renderer/entities/network/model/network-model.ts index 2714f0aeb6..5ffb03998f 100644 --- a/src/renderer/entities/network/model/network-model.ts +++ b/src/renderer/entities/network/model/network-model.ts @@ -1,7 +1,7 @@ import { type ApiPromise } from '@polkadot/api'; import { type VoidFn } from '@polkadot/api/types'; import { createEffect, createEvent, createStore, sample, scopeBind } from 'effector'; -import { spread } from 'patronum'; +import { combineEvents, spread } from 'patronum'; import { ProviderType, @@ -19,10 +19,9 @@ import { ConnectionStatus, ConnectionType, type ID, - type Metadata, type NoID, } from '@/shared/core'; -import { series } from '@/shared/effector'; +import { createBuffer, series } from '@/shared/effector'; import { dictionary, nonNullable } from '@/shared/lib/utils'; import { networkUtils } from '../lib/network-utils'; @@ -66,36 +65,30 @@ type MetadataSubResult = { chainId: ChainId; unsubscribe: VoidFn; }; -const subscribeMetadataFx = createEffect(async (api: ApiPromise): Promise => { - const unsubscribe = await metadataService.subscribeMetadata(api, requestMetadataFx); - - return { chainId: api.genesisHash.toHex(), unsubscribe }; -}); +const subscribeRuntimeVersionFx = createEffect( + async ({ api, cachedVersion }: { api: ApiPromise; cachedVersion: number | null }): Promise => { + const unsubscribe = await metadataService.subscribeRuntimeVersion({ + api, + cachedRuntimeVersion: cachedVersion, + callback: removeMetadata, + }); -const requestMetadataFx = createEffect((api: ApiPromise): Promise> => { - return metadataService.requestMetadata(api); -}); + return { chainId: api.genesisHash.toHex(), unsubscribe }; + }, +); const unsubscribeMetadataFx = createEffect((unsubscribe: VoidFn) => { unsubscribe(); }); -const saveMetadataFx = createEffect((metadata: NoID): Promise => { - return storageService.metadata.put(metadata); +const saveMetadataFx = createEffect((metadata: NoID[]): Promise => { + return storageService.metadata.createAll(metadata); }); const removeMetadataFx = createEffect((ids: ID[]): Promise => { return storageService.metadata.deleteAll(ids); }); -type ProviderMetadataParams = { - provider: ProviderWithMetadata; - metadata: Metadata; -}; -const updateProviderMetadataFx = createEffect(({ provider, metadata }: ProviderMetadataParams) => { - provider.updateMetadata(metadata); -}); - type CreateProviderParams = { chainId: ChainId; nodes: string[]; @@ -118,7 +111,7 @@ const createProviderFx = createEffect( const provider = networkService.createProvider( chainId, providerType, - { nodes, metadata: metadata?.metadata }, + { nodes, metadata }, { onConnected: () => { if (DEBUG_NETWORKS) { @@ -141,6 +134,10 @@ const createProviderFx = createEffect( }, ); + provider.onMetadataReceived(({ metadata, metadataVersion, runtimeVersion }) => { + metadataReceived({ chainId, metadata, metadataVersion, runtimeVersion }); + }); + if (providerType === ProviderType.LIGHT_CLIENT) { /** * HINT: Light Client provider must be connected manually GitHub Light @@ -156,18 +153,17 @@ const createProviderFx = createEffect( type CreateApiParams = { chainId: ChainId; - providers: Record; - apis: Record; + provider: ProviderWithMetadata; + existingApi: ApiPromise | null; }; -const createApiFx = createEffect(async ({ chainId, providers, apis }: CreateApiParams): Promise => { - if (chainId in apis) { - const api = apis[chainId]; - await api.connect(); +const createApiFx = createEffect(async ({ chainId, provider, existingApi }: CreateApiParams): Promise => { + if (existingApi) { + await existingApi.connect(); - return api; + return existingApi; } - return networkService.createApi(chainId, providers[chainId]); + return networkService.createApi(chainId, provider); }); type DisconnectParams = { @@ -226,8 +222,13 @@ sample({ target: $connections, }); +const readyToConnect = combineEvents({ + events: [populateConnectionsFx.done, populateMetadataFx.done, populateChainsFx.done], + reset: networkStarted, +}); + sample({ - clock: populateConnectionsFx.doneData, + clock: readyToConnect, source: $chains, fn: (chains) => { return Object.keys(chains).map((chainId) => chainId as ChainId); @@ -294,7 +295,11 @@ sample({ sample({ clock: connected, source: { providers: $providers, apis: $apis }, - fn: ({ providers, apis }, chainId) => ({ chainId, providers, apis }), + fn: ({ providers, apis }, chainId) => ({ + chainId, + provider: providers[chainId], + existingApi: apis[chainId] ?? null, + }), target: createApiFx, }); @@ -366,13 +371,35 @@ sample({ // ================ Metadata section =================== // ===================================================== +const metadataReceived = createEvent>(); +const saveMetadata = createBuffer({ source: metadataReceived, timeframe: 2000 }); +const removeMetadata = createEvent(); + sample({ - clock: createApiFx.doneData, - target: subscribeMetadataFx, + clock: removeMetadata, + source: $metadata, + fn: (list, removed) => { + return list.filter((x) => x.chainId === removed.genesisHash.toHex()).map((x) => x.id); + }, + target: removeMetadataFx, +}); + +sample({ + clock: createApiFx.done, + source: $metadata, + fn: (metadata, { params, result }) => { + const cachedVersion = metadata.find((m) => m.chainId === params.chainId)?.runtimeVersion ?? null; + + return { + api: result, + cachedVersion, + }; + }, + target: subscribeRuntimeVersionFx, }); sample({ - clock: subscribeMetadataFx.doneData, + clock: subscribeRuntimeVersionFx.doneData, source: $metadataSubscriptions, fn: (subscriptions, { chainId, unsubscribe }) => ({ ...subscriptions, @@ -415,28 +442,21 @@ sample({ }); sample({ - clock: requestMetadataFx.doneData, - source: $metadata, - filter: (metadata, newMetadata) => { - return metadata.every(({ chainId, version }) => { - return chainId !== newMetadata.chainId || version !== newMetadata.version; - }); - }, - fn: (_, metadata) => metadata, + clock: saveMetadata, target: saveMetadataFx, }); sample({ clock: saveMetadataFx.doneData, source: $metadata, - filter: (_, newMetadata) => Boolean(newMetadata), + filter: (_, newMetadata) => nonNullable(newMetadata), fn: (metadata, newMetadata) => { - const oldMetadata = metadata.filter(({ chainId }) => chainId === newMetadata!.chainId).map(({ id }) => id); - const cleanMetadata = metadata.filter(({ chainId }) => chainId !== newMetadata!.chainId); + const oldMetadata = metadata.filter(({ chainId }) => newMetadata!.find((m) => m.chainId === chainId)); + const cleanMetadata = metadata.filter((x) => !oldMetadata.includes(x)); return { - metadata: [...cleanMetadata, newMetadata!], - oldMetadata, + metadata: cleanMetadata.concat(newMetadata!), + oldMetadata: oldMetadata.map((x) => x.id), }; }, target: spread({ @@ -445,17 +465,6 @@ sample({ }), }); -sample({ - clock: saveMetadataFx.doneData, - source: $providers, - filter: (_, metadata) => nonNullable(metadata), - fn: (providers, metadata) => ({ - provider: providers[metadata!.chainId], - metadata: metadata!.metadata, - }), - target: updateProviderMetadataFx, -}); - export const networkModel = { $chains, $apis, diff --git a/src/renderer/shared/api/network/__tests__/metadataService.test.ts b/src/renderer/shared/api/network/__tests__/metadataService.test.ts index bd977f5aa2..8d21292f9e 100644 --- a/src/renderer/shared/api/network/__tests__/metadataService.test.ts +++ b/src/renderer/shared/api/network/__tests__/metadataService.test.ts @@ -3,32 +3,18 @@ import { type ApiPromise } from '@polkadot/api'; import { metadataService } from '../service/metadataService'; describe('shared/api/network/services/metadataService', () => { - test('should return UnsubscribePromise on subscribeMetadata', async () => { + test('should return UnsubscribePromise on subscribeRuntimeVersion', async () => { const unsub = () => 5; const apiMock = { rpc: { state: { subscribeRuntimeVersion: () => Promise.resolve(unsub) } }, } as unknown as ApiPromise; - const result = await metadataService.subscribeMetadata(apiMock, () => {}); + const result = await metadataService.subscribeRuntimeVersion({ + api: apiMock, + cachedRuntimeVersion: null, + callback: () => {}, + }); expect(result).toEqual(unsub); expect(unsub()).toEqual(5); }); - - test('should return metadata on requestMetadata', async () => { - const version = { specVersion: { toNumber: () => 5 } }; - const metadata = { toHex: () => '0x11' }; - - const apiMock = { - genesisHash: { toHex: () => '0x00' }, - rpc: { - state: { - getMetadata: () => Promise.resolve(metadata), - getRuntimeVersion: () => Promise.resolve(version), - }, - }, - } as unknown as ApiPromise; - - const result = await metadataService.requestMetadata(apiMock); - expect(result).toEqual({ metadata: '0x11', version: 5, chainId: '0x00' }); - }); }); diff --git a/src/renderer/shared/api/network/lib/types.ts b/src/renderer/shared/api/network/lib/types.ts index 0e864a1734..71cce4e2fc 100644 --- a/src/renderer/shared/api/network/lib/types.ts +++ b/src/renderer/shared/api/network/lib/types.ts @@ -14,6 +14,12 @@ export type Subscription = { cb: ProviderInterfaceCallback; }; +export type ProviderMetadata = { + metadata: HexString; + metadataVersion: number; + runtimeVersion: number; +}; + export interface ProviderWithMetadata extends ProviderInterface { - updateMetadata: (metadata: HexString) => void; + onMetadataReceived: (callback: (value: ProviderMetadata) => unknown) => VoidFunction; } diff --git a/src/renderer/shared/api/network/provider/CachedProvider.ts b/src/renderer/shared/api/network/provider/CachedProvider.ts index 820752b4f0..90eef47a36 100644 --- a/src/renderer/shared/api/network/provider/CachedProvider.ts +++ b/src/renderer/shared/api/network/provider/CachedProvider.ts @@ -1,23 +1,81 @@ import { type ProviderInterface } from '@polkadot/rpc-provider/types'; +import { isString } from 'lodash'; +import mitt from 'mitt'; -import { type HexString } from '@/shared/core'; -import { GET_METADATA_METHOD } from '../lib/constants'; -import { type ProviderWithMetadata } from '../lib/types'; +import { type ChainMetadata } from '@/shared/core'; +import { scaleEncodedToNumber } from '@/shared/lib/utils'; +import { type ProviderMetadata, type ProviderWithMetadata } from '../lib/types'; -export function createCachedProvider(Provider: new (...args: any[]) => ProviderInterface, metadata?: HexString) { +const LEGACY_METADATA_VERSION = 14; +const STATE_CALL_METHOD = 'state_call'; + +export function createCachedProvider(Provider: new (...args: any[]) => ProviderInterface, metadata?: ChainMetadata) { class CachedProvider extends Provider implements ProviderWithMetadata { - private metadata: HexString | undefined = metadata; + private metadata: ProviderMetadata | null = metadata || null; + private events = mitt<{ metadataReceived: ProviderMetadata }>(); - updateMetadata(metadata: HexString) { + private updateMetadata(metadata: ProviderMetadata) { this.metadata = metadata; + this.events.emit('metadataReceived', metadata); + } + + onMetadataReceived(callback: (value: ProviderMetadata) => unknown): VoidFunction { + this.events.on('metadataReceived', callback); + + return () => this.events.off('metadataReceived', callback); + } + + stateCall(method: string, ...args: unknown[]) { + return super.send(STATE_CALL_METHOD, [method, ...args]); + } + + getRuntimeVersion() { + return super.send('state_getRuntimeVersion', []); } async send(method: string, params: unknown[], ...args: any[]): Promise { - const hasMetadata = Boolean(this.metadata); - const isMetadataMethod = method === GET_METADATA_METHOD; const hasParams = params.length > 0; - return hasMetadata && isMetadataMethod && !hasParams ? this.metadata : super.send(method, params, ...args); + if (method === 'state_getMetadata' && !hasParams) { + if (this.metadata) { + return Promise.resolve(this.metadata.metadata); + } + + const metadata = await super.send(method, params, ...args); + const runtimeVersion = await this.getRuntimeVersion(); + + this.updateMetadata({ + runtimeVersion: runtimeVersion.specVersion, + metadataVersion: LEGACY_METADATA_VERSION, + metadata, + }); + + return metadata; + } + + if (method === STATE_CALL_METHOD && hasParams) { + const [call, rawVersion] = params; + + if (call === 'Metadata_metadata_at_version') { + const metadataVersion = isString(rawVersion) ? scaleEncodedToNumber(rawVersion) : 0; + if (metadataVersion === this.metadata?.metadataVersion) { + return Promise.resolve(this.metadata.metadata); + } + + const metadata = await super.send(method, params, ...args); + const runtimeVersion = await this.getRuntimeVersion(); + + this.updateMetadata({ + runtimeVersion: runtimeVersion.specVersion, + metadataVersion, + metadata, + }); + + return metadata; + } + } + + return super.send(method, params, ...args); } } diff --git a/src/renderer/shared/api/network/service/metadataService.ts b/src/renderer/shared/api/network/service/metadataService.ts index d907a0537f..8712cea3b0 100644 --- a/src/renderer/shared/api/network/service/metadataService.ts +++ b/src/renderer/shared/api/network/service/metadataService.ts @@ -1,23 +1,26 @@ import { type ApiPromise } from '@polkadot/api'; import { type UnsubscribePromise } from '@polkadot/api/types'; -import { type ChainMetadata, type NoID } from '@/shared/core'; - export const metadataService = { - requestMetadata, - subscribeMetadata, + subscribeRuntimeVersion, +}; + +type SubscribeParams = { + api: ApiPromise; + cachedRuntimeVersion: number | null; + callback: (api: ApiPromise) => void; }; -async function requestMetadata(api: ApiPromise): Promise> { - const [metadata, version] = await Promise.all([api.rpc.state.getMetadata(), api.rpc.state.getRuntimeVersion()]); +function subscribeRuntimeVersion({ api, cachedRuntimeVersion, callback }: SubscribeParams): UnsubscribePromise { + let currectVersion = cachedRuntimeVersion ?? null; - return { - metadata: metadata.toHex(), - version: version.specVersion.toNumber(), - chainId: api.genesisHash.toHex(), - }; -} + return api.rpc.state.subscribeRuntimeVersion((version) => { + const receivedVersion = version.specVersion.toNumber(); + if (!currectVersion || receivedVersion > currectVersion) { + console.info(`Runtime version upgrade: ${currectVersion ?? 'empty'} -> ${receivedVersion}`); -function subscribeMetadata(api: ApiPromise, callback: (api: ApiPromise) => void): UnsubscribePromise { - return api.rpc.state.subscribeRuntimeVersion(() => callback(api)); + currectVersion = receivedVersion; + callback(api); + } + }); } diff --git a/src/renderer/shared/api/network/service/networkService.ts b/src/renderer/shared/api/network/service/networkService.ts index 9c1dd6db04..e807411ade 100644 --- a/src/renderer/shared/api/network/service/networkService.ts +++ b/src/renderer/shared/api/network/service/networkService.ts @@ -3,7 +3,7 @@ import { type ProviderInterface } from '@polkadot/rpc-provider/types'; import * as Sc from '@substrate/connect'; import { EXTENSIONS } from '@/shared/config/extensions'; -import { type ChainId, type HexString } from '@/shared/core'; +import { type ChainId, type ChainMetadata } from '@/shared/core'; import { getKnownChain } from '@/shared/lib/utils'; import { ProviderType, type ProviderWithMetadata } from '../lib/types'; import { createCachedProvider } from '../provider/CachedProvider'; @@ -27,7 +27,7 @@ function createApi(chainId: ChainId, provider: ProviderInterface): Promise void; @@ -58,7 +58,7 @@ function createProvider( return provider; } -function createSubstrateProvider(chainId: ChainId, metadata?: HexString): ProviderWithMetadata | undefined { +function createSubstrateProvider(chainId: ChainId, metadata?: ChainMetadata): ProviderWithMetadata | undefined { const knownChainId = getKnownChain(chainId); if (knownChainId) { diff --git a/src/renderer/shared/api/storage/service/dexie.ts b/src/renderer/shared/api/storage/service/dexie.ts index fff6616138..11c6c738a6 100644 --- a/src/renderer/shared/api/storage/service/dexie.ts +++ b/src/renderer/shared/api/storage/service/dexie.ts @@ -80,6 +80,14 @@ class DexieStorage extends Dexie { basketTransactions: '++id', }); + this.version(24).stores({ + metadata: null, + }); + + this.version(25).stores({ + metadata: '++id', + }); + this.connections = this.table('connections'); this.balances = this.table('balances'); this.wallets = this.table('wallets'); diff --git a/src/renderer/shared/core/types/metadata.ts b/src/renderer/shared/core/types/metadata.ts index f89c8d2748..41a33318cb 100644 --- a/src/renderer/shared/core/types/metadata.ts +++ b/src/renderer/shared/core/types/metadata.ts @@ -8,6 +8,7 @@ import { type ChainId, type ID, type Metadata } from './general'; export type ChainMetadata = { id: ID; chainId: ChainId; - version: number; + runtimeVersion: number; + metadataVersion: number; metadata: Metadata; }; diff --git a/src/renderer/shared/effector/helpers/createBuffer.ts b/src/renderer/shared/effector/helpers/createBuffer.ts new file mode 100644 index 0000000000..da9d7dc544 --- /dev/null +++ b/src/renderer/shared/effector/helpers/createBuffer.ts @@ -0,0 +1,52 @@ +import { type Event, type Unit, createEvent, createStore, sample } from 'effector'; +import { delay, readonly } from 'patronum'; + +type Params = { + source: Unit; + timeframe: number; +}; + +/** + * Collect values from source unit to buffer and flush them after given timeout. + * This implementation doesn't have any debounce/throttle logic. + * + * @example + * ```ts + * // Pushing new values to list + * const flush = createBuffer({ source: createItem, timeframe: 1000 }); + * + * sample({ + * clock: flush, + * source: $list, + * fn: (existing, buffer) => existing.concat(buffer), + * target: $list, + * }); + * ```; + */ +export const createBuffer = ({ source, timeframe }: Params) => { + const $buffer = createStore([]); + const call = createEvent(); + const flush = delay({ source, timeout: timeframe }) as Event; + + sample({ + clock: source, + source: $buffer, + fn: (buffer, value) => buffer.concat(value), + target: $buffer, + }); + + sample({ + clock: flush, + source: $buffer, + filter: (buffer) => buffer.length > 0, + target: call, + }); + + sample({ + clock: flush, + fn: () => [], + target: $buffer, + }); + + return readonly(call); +}; diff --git a/src/renderer/shared/effector/index.ts b/src/renderer/shared/effector/index.ts index 9a39b47a4f..f9fc8c9fd0 100644 --- a/src/renderer/shared/effector/index.ts +++ b/src/renderer/shared/effector/index.ts @@ -4,3 +4,4 @@ export { createDataSubscription, createPagesHandler } from './createDataSubscrip export { attachToFeatureInput } from './attachToFeatureInput'; export { series } from './helpers/series'; +export { createBuffer } from './helpers/createBuffer'; diff --git a/src/renderer/shared/lib/utils/substrate.ts b/src/renderer/shared/lib/utils/substrate.ts index b53878fe2c..4bf97348c0 100644 --- a/src/renderer/shared/lib/utils/substrate.ts +++ b/src/renderer/shared/lib/utils/substrate.ts @@ -1,6 +1,6 @@ import { type ApiPromise } from '@polkadot/api'; import { type u32 } from '@polkadot/types'; -import { type BN, BN_TWO, bnMin, hexToU8a, isHex } from '@polkadot/util'; +import { type BN, BN_TWO, bnMin, hexToU8a, isHex, numberToU8a, u8aToHex, u8aToNumber } from '@polkadot/util'; import { blake2AsHex } from '@polkadot/util-crypto'; import { type BaseTxInfo, @@ -249,3 +249,7 @@ export const getSecondsDurationToBlock = (timeToBlock: number): number => { return Math.floor(time / 1000); }; + +export const numberToScaleEncoded = (value: number) => u8aToHex(numberToU8a(value)); + +export const scaleEncodedToNumber = (value: string) => u8aToNumber(hexToU8a(value));