Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09fbbaed49 | |||
| d6bb0979d0 | |||
| fa1d47e941 | |||
| 44ec6d6bc8 | |||
| 6d47046a38 | |||
| 5cfd09cd99 | |||
| 40b4670d80 | |||
| 79603d61d7 | |||
| e8e9a70ef4 |
@@ -0,0 +1,12 @@
|
||||
# Shared assets
|
||||
|
||||
This directory contains asset files shared by many projects in this repo.
|
||||
|
||||
You will find:
|
||||
|
||||
- favicons
|
||||
- logos
|
||||
- shared fonts
|
||||
- shared icon SVGs
|
||||
|
||||
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
|
||||
|
After Width: | Height: | Size: 545 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
|
||||
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
|
||||
font-weight: 100 1000;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
Update fonts by doing the following:
|
||||
|
||||
1. Go to https://fonts.google.com/specimen/Open+Sans
|
||||
2. Add all the styles you want and select `@import`
|
||||
3. Copy the url (e.g. curl https://fonts.googleapis.com/css2\?family\=Open+Sans:ital,wght@0,300\;0,400\;0,500\;0,600\;0,700\;0,800\;1,300\;1,400\;1,500\;1,600\;1,700\;1,800\&display\=swap)
|
||||
4. Run `curl curl https://fonts.googleapis.com/css2\?family\=Open+Sans:ital,wght@0,300\;0,400\;0,500\;0,600\;0,700\;0,800\;1,300\;1,400\;1,500\;1,600\;1,700\;1,800\&display\=swap`
|
||||
5. Use the response as the CSS import directives and download the font files for each font weight
|
||||
6. Remember to delete any old font files
|
||||
@@ -0,0 +1,96 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4n.ttf) format('truetype');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 80 80"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_421_13045)">
|
||||
<path
|
||||
d="M40 80C62.0914 80 80 62.0914 80 40C80 17.9086 62.0914 0 40 0C17.9086 0 0 17.9086 0 40C0 62.0914 17.9086 80 40 80Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M40 77.2636C60.5801 77.2636 77.2636 60.5801 77.2636 40C77.2636 19.4199 60.5801 2.73645 40 2.73645C19.4199 2.73645 2.73645 19.4199 2.73645 40C2.73645 60.5801 19.4199 77.2636 40 77.2636Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M24.0224 32.471H23.9776V32.5084V45.5775L18.4673 32.4934L18.4598 32.471H18.4299H15.3047H11.7981H11.7607V32.5084V47.4916V47.529H11.7981H15.3047H15.3421V47.4916V34.4L20.8748 47.5065L20.8822 47.529H20.9121H24.0224H27.5215H27.5589V47.4916V32.5084V32.471H27.5215H24.0224Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M23.8965 32.39H27.64V47.6101H20.8238L20.7989 47.5352L15.4232 34.8006V47.6101H11.6797V32.39H18.5183L18.5432 32.4649L23.8965 45.1761V32.39ZM23.9776 45.5776L18.4673 32.4935L18.4598 32.471H11.7608V47.529H15.3421V34.4L20.8748 47.5065L20.8823 47.529H27.5589V32.471H23.9776V45.5776Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M68.2019 32.471H61.5178H61.4804L61.4729 32.5009L58.0486 45.6374L54.6169 32.5009L54.6094 32.471H54.5795H47.8804H47.8355V32.5084V47.4916V47.529H47.8804H51.3795H51.4169V47.4916V34.5047L54.8038 47.499L54.8112 47.529H54.8486H61.2337H61.2636L61.2711 47.499L64.658 34.5047V47.4916V47.529H64.6954H68.2019H68.2393V47.4916V32.5084V32.471H68.2019Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M61.4171 32.39H68.3204V47.6101H64.5769V35.1372L61.3497 47.5187L61.3495 47.5195L61.3269 47.6101H54.7479L54.7253 47.5195L54.7251 47.5187L51.4979 35.1372V47.6101H47.7545V32.39H54.6727L54.6953 32.4804L54.6955 32.4813L58.0485 45.3163L61.3943 32.4813L61.3945 32.4805L61.4171 32.39ZM58.0486 45.6374L54.6168 32.5009L54.6094 32.471H47.8355V47.529H51.4168V34.5047L54.8038 47.4991L54.8112 47.529H61.2636L61.2711 47.4991L64.658 34.5047V47.529H68.2393V32.471H61.4804L61.4729 32.5009L58.0486 45.6374Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M42.0711 32.471H42.0486L42.0412 32.486L37.7869 39.8804L33.5103 32.486L33.5028 32.471H33.4804H29.4355H29.3608L29.3982 32.5308L35.9851 43.9402V47.4916V47.529H36.0225H39.529H39.5664V47.4916V43.9402L46.1533 32.5308L46.1907 32.471H46.1159H42.0711Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M41.9985 32.39H46.337L46.2228 32.5726L39.6475 43.9619V47.6101H35.904V43.9619L29.3286 32.5726L29.2145 32.39H33.5529L33.5817 32.4475L37.7868 39.7181L41.9697 32.4476L41.9985 32.39ZM42.0411 32.486L37.7869 39.8804L33.5103 32.486L33.5028 32.471H29.3608L29.3981 32.5308L35.9851 43.9402V47.529H39.5664V43.9402L46.1533 32.5308L46.1907 32.471H42.0486L42.0411 32.486Z"
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_421_13045">
|
||||
<rect width="80" height="80" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 714 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
|
||||
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
|
||||
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
|
||||
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
|
||||
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#FC1D60"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1011 B |
@@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#fff"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#000"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#fff"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#141521"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.57"
|
||||
version = "1.1.58"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.57"
|
||||
version = "1.1.58"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -124,7 +124,7 @@ impl NymApiTopologyProvider {
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes_v2();
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata();
|
||||
|
||||
let (rewarded_set, mixnodes_res, gateways_res) =
|
||||
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
|
||||
|
||||
@@ -107,7 +107,10 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client.get_all_basic_entry_assigned_nodes_v2().await?.nodes;
|
||||
let gateways = client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await?
|
||||
.nodes;
|
||||
info!("nym api reports {} gateways", gateways.len());
|
||||
|
||||
log::trace!("Gateways: {:#?}", gateways);
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct Config {
|
||||
pub maximum_reconnection_backoff: Duration,
|
||||
pub initial_connection_timeout: Duration,
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
pub use_legacy_packet_encoding: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -36,12 +37,14 @@ impl Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_packet_encoding: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_packet_encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +270,12 @@ impl SendWithoutResponse for Client {
|
||||
fn send_without_response(&self, packet: MixPacket) -> io::Result<()> {
|
||||
let address = packet.next_hop_address();
|
||||
trace!("Sending packet to {address}");
|
||||
let framed_packet = FramedNymPacket::from(packet);
|
||||
|
||||
// TODO: optimisation for the future: rather than constantly using legacy encoding,
|
||||
// once we're addressing by node_id (and thus have full node info here),
|
||||
// we could simply infer supported encoding based on their version
|
||||
let framed_packet =
|
||||
FramedNymPacket::from_mix_packet(packet, self.config.use_legacy_packet_encoding);
|
||||
|
||||
let Some(sender) = self.active_connections.get_mut(&address) else {
|
||||
// there was never a connection to begin with
|
||||
@@ -328,6 +336,7 @@ mod tests {
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_packet_encoding: false,
|
||||
},
|
||||
NoiseConfig::new(
|
||||
Arc::new(x25519::KeyPair::new(&mut rng)),
|
||||
|
||||
@@ -471,12 +471,12 @@ impl NymApiClient {
|
||||
pub async fn get_all_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
self.get_all_basic_entry_assigned_nodes_v2()
|
||||
self.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
}
|
||||
|
||||
pub async fn get_all_basic_entry_assigned_nodes_v2(
|
||||
pub async fn get_all_basic_entry_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
collect_paged_skimmed_v2!(self, get_basic_entry_assigned_nodes_v2)
|
||||
|
||||
@@ -389,7 +389,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"entry-gateways",
|
||||
"all",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
|
||||
@@ -131,7 +131,8 @@ impl NoiseConfig {
|
||||
}
|
||||
|
||||
// Only for phased update
|
||||
//SW This can lead to some troubles if two nodes shares the same IP and one support Noise but not the other. This in only for the progressive update though and there is no workaround
|
||||
//SW This can lead to some troubles if two nodes share the same IP and one support Noise but not the other.
|
||||
// This in only for the progressive update though and there is no workaround
|
||||
pub(crate) fn get_noise_support(&self, ip_addr: IpAddr) -> Option<NoiseVersion> {
|
||||
let plain_ip_support = self.network.support.inner.load().get(&ip_addr).copied();
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ use bytes::{BufMut, BytesMut};
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
use nym_sphinx_params::key_rotation::SphinxKeyRotation;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::packet_version::{PacketVersion, CURRENT_PACKET_VERSION};
|
||||
use nym_sphinx_params::packet_version::{
|
||||
PacketVersion, CURRENT_PACKET_VERSION, LEGACY_PACKET_VERSION,
|
||||
};
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_sphinx_types::NymPacket;
|
||||
|
||||
@@ -19,26 +21,25 @@ pub struct FramedNymPacket {
|
||||
pub(crate) packet: NymPacket,
|
||||
}
|
||||
|
||||
impl From<MixPacket> for FramedNymPacket {
|
||||
fn from(packet: MixPacket) -> Self {
|
||||
let typ = packet.packet_type();
|
||||
let rot = packet.key_rotation();
|
||||
FramedNymPacket::new(packet.into_packet(), typ, rot)
|
||||
}
|
||||
}
|
||||
|
||||
impl FramedNymPacket {
|
||||
pub fn new(
|
||||
packet: NymPacket,
|
||||
packet_type: PacketType,
|
||||
key_rotation: SphinxKeyRotation,
|
||||
use_legacy_packet_encoding: bool,
|
||||
) -> Self {
|
||||
// If this fails somebody is using the library in a super incorrect way, because they
|
||||
// already managed to somehow create a sphinx packet
|
||||
let packet_size = PacketSize::get_type(packet.len()).unwrap();
|
||||
|
||||
let packet_version = if use_legacy_packet_encoding {
|
||||
LEGACY_PACKET_VERSION
|
||||
} else {
|
||||
PacketVersion::new()
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::new(),
|
||||
packet_version,
|
||||
packet_size,
|
||||
key_rotation,
|
||||
packet_type,
|
||||
@@ -47,6 +48,12 @@ impl FramedNymPacket {
|
||||
FramedNymPacket { header, packet }
|
||||
}
|
||||
|
||||
pub fn from_mix_packet(packet: MixPacket, use_legacy_packet_encoding: bool) -> Self {
|
||||
let typ = packet.packet_type();
|
||||
let rot = packet.key_rotation();
|
||||
FramedNymPacket::new(packet.into_packet(), typ, rot, use_legacy_packet_encoding)
|
||||
}
|
||||
|
||||
pub fn header(&self) -> Header {
|
||||
self.header
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use thiserror::Error;
|
||||
// - packet_version (starting with v1.1.0)
|
||||
// - packet_size indicator
|
||||
// - packet_type
|
||||
// - sphinx key rotation (starting with v1.12.0/v1.13.0 - either Cheddar or Dolcelatte release)
|
||||
// - sphinx key rotation (starting with v1.13.0 - the Dolcelatte release)
|
||||
|
||||
// it also just so happens that the only valid values for packet_size indicator include values 1-6
|
||||
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
|
||||
@@ -22,6 +22,9 @@ pub const CURRENT_PACKET_VERSION_NUMBER: u8 = KEY_ROTATION_VERSION_NUMBER;
|
||||
pub const CURRENT_PACKET_VERSION: PacketVersion =
|
||||
PacketVersion::unchecked(CURRENT_PACKET_VERSION_NUMBER);
|
||||
|
||||
pub const LEGACY_PACKET_VERSION: PacketVersion =
|
||||
PacketVersion::unchecked(INITIAL_PACKET_VERSION_NUMBER);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct PacketVersion(u8);
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ pub async fn current_network_topology_async(
|
||||
let metadata = mixnodes_res.metadata;
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
|
||||
let gateways_res = api_client.get_all_basic_entry_assigned_nodes_v2().await?;
|
||||
let gateways_res = api_client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await?;
|
||||
if gateways_res.metadata != metadata {
|
||||
console_warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
return Err(WasmCoreError::UnavailableNetworkTopology);
|
||||
|
||||
@@ -419,6 +419,13 @@ impl<R, S> FreshHandler<R, S> {
|
||||
return Ok(INITIAL_PROTOCOL_VERSION);
|
||||
};
|
||||
|
||||
// #####
|
||||
// On backwards compat:
|
||||
// Currently it is the case that gateways will understand all previous protocol versions
|
||||
// and will downgrade accordingly, but this will now always be the case.
|
||||
// For example, once we remove downgrade on legacy auth, anything below version 4 will be rejected
|
||||
// #####
|
||||
|
||||
// a v2 gateway will understand v1 requests, but v1 client will not understand v2 responses
|
||||
if client_protocol_version == 1 {
|
||||
return Ok(1);
|
||||
@@ -434,6 +441,11 @@ impl<R, S> FreshHandler<R, S> {
|
||||
return Ok(3);
|
||||
}
|
||||
|
||||
// a v5 gateway will understand v4 requests (key-rotation)
|
||||
if client_protocol_version == 4 {
|
||||
return Ok(4);
|
||||
}
|
||||
|
||||
// we can't handle clients with higher protocol than ours
|
||||
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
|
||||
if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.60"
|
||||
version = "1.1.61"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -90,8 +90,8 @@ impl KeyRotationController {
|
||||
async fn handle_contract_cache_update(&mut self) {
|
||||
let updated = self.get_contract_data().await;
|
||||
|
||||
info!(
|
||||
"current rotation: {}",
|
||||
debug!(
|
||||
"current key rotation: {}",
|
||||
updated
|
||||
.key_rotation_state
|
||||
.key_rotation_id(updated.interval.current_epoch_absolute_id())
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(crate) async fn mixnodes_basic_active(
|
||||
tag = "Unstable Nym Nodes",
|
||||
get,
|
||||
params(NodesParams),
|
||||
path = "/entry-gateways/all",
|
||||
path = "/entry-gateways",
|
||||
context_path = "/v2/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
@@ -124,7 +124,7 @@ pub(crate) async fn entry_gateways_basic_all(
|
||||
tag = "Unstable Nym Nodes",
|
||||
get,
|
||||
params(NodesParams),
|
||||
path = "/exit-gateways/all",
|
||||
path = "/exit-gateways",
|
||||
context_path = "/v2/unstable/nym-nodes/skimmed",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.4.0"
|
||||
probe_git_ref="nym-vpn-core-v1.10.0"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
monorepo_root=$(realpath "${crate_root}/../..")
|
||||
@@ -21,7 +21,7 @@ export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1,mnemonic=${{ secrets.CANARY_PROBE_MNEMONIC }}"
|
||||
|
||||
workers=${1:-1}
|
||||
echo "Running $workers workers in parallel"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "2.3.3"
|
||||
version = "3.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -17,29 +17,46 @@ ammonia = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true, features = ["tokio", "macros"] }
|
||||
bip39 = { workspace = true }
|
||||
celes = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
envy = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
moka = { workspace = true, features = ["future"] }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
|
||||
nym-node-status-client = { path = "../nym-node-status-client" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"]}
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-serde-helpers = { path = "../../common/serde-helpers" }
|
||||
nym-statistics-common = { path = "../../common/statistics" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
|
||||
# TODO dz had to switch to cheddar versions because develop is ahead of current
|
||||
# Nym API: revert after Cheddar is out
|
||||
nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-node-status-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["middleware"]}
|
||||
nym-network-defaults = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-serde-helpers = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-statistics-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
# nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
# nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
|
||||
# nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
|
||||
# nym-node-status-client = { path = "../nym-node-status-client" }
|
||||
# nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
# nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
# nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"]}
|
||||
# nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
# nym-serde-helpers = { path = "../../common/serde-helpers" }
|
||||
# nym-statistics-common = { path = "../../common/statistics" }
|
||||
# nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-validator-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
# nym-task = { path = "../../common/task" }
|
||||
# nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
|
||||
nym-task = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-node-requests = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_json_path = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
-- for a couple of days after migrating chrono -> time, we stored dates as
|
||||
-- 2025-June-DD instead of 2025-06-DD. This migration fixes those entries.
|
||||
--
|
||||
-- Because of a UNIQUE constraint on (node_id, date_utc), we can't just rename in-place.
|
||||
-- - merge (add) node stats back to the original table where conflict (node_id, date_utc) would exist
|
||||
-- - delete invalid records from original table (those stats were merged into correct rows above)
|
||||
-- - insert rows that did not have a conflicting (node_Id, date_utc) combo
|
||||
-- Conflicts affect only the date which has both kinds of entries,
|
||||
-- e.g. 2025-06-05 and 2025-June-05 (date when this change was deployed)
|
||||
--
|
||||
-- This applies to both affected tables.
|
||||
|
||||
-- ----------------------------------------
|
||||
-- mixnode_daily_stats
|
||||
-- ----------------------------------------
|
||||
|
||||
-- First, copy over rows with invalid date to a temp table (in the correct date format)
|
||||
CREATE TEMP TABLE tmp_mix AS
|
||||
SELECT
|
||||
mix_id,
|
||||
REPLACE(date_utc,'June','06') AS new_date,
|
||||
SUM(total_stake) AS total_stake_sum,
|
||||
SUM(packets_received) AS packets_received_sum,
|
||||
SUM(packets_sent) AS packets_sent_sum,
|
||||
SUM(packets_dropped) AS packets_dropped_sum
|
||||
FROM mixnode_daily_stats
|
||||
WHERE date_utc LIKE '%June%'
|
||||
GROUP BY mix_id, new_date;
|
||||
|
||||
UPDATE mixnode_daily_stats AS m
|
||||
SET
|
||||
total_stake = m.total_stake,
|
||||
packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc),
|
||||
packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc),
|
||||
packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc)
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM tmp_mix
|
||||
WHERE mix_id = m.mix_id
|
||||
AND new_date = m.date_utc
|
||||
);
|
||||
|
||||
DELETE FROM mixnode_daily_stats
|
||||
WHERE date_utc LIKE '%June%';
|
||||
|
||||
INSERT INTO mixnode_daily_stats
|
||||
(mix_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped)
|
||||
SELECT
|
||||
mix_id,
|
||||
new_date,
|
||||
total_stake_sum,
|
||||
packets_received_sum,
|
||||
packets_sent_sum,
|
||||
packets_dropped_sum
|
||||
FROM tmp_mix AS t
|
||||
-- only those whose new_date did _not_ already exist
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM mixnode_daily_stats AS m
|
||||
WHERE m.mix_id = t.mix_id
|
||||
AND m.date_utc = t.new_date
|
||||
);
|
||||
|
||||
DROP TABLE tmp_mix;
|
||||
|
||||
|
||||
-- ----------------------------------------
|
||||
-- nym_node_daily_mixing_stats
|
||||
-- ----------------------------------------
|
||||
|
||||
CREATE TEMP TABLE tmp_nym_node_stats AS
|
||||
SELECT
|
||||
node_id,
|
||||
REPLACE(date_utc,'June','06') AS new_date,
|
||||
SUM(total_stake) AS total_stake_sum,
|
||||
SUM(packets_received) AS packets_received_sum,
|
||||
SUM(packets_sent) AS packets_sent_sum,
|
||||
SUM(packets_dropped) AS packets_dropped_sum
|
||||
FROM nym_node_daily_mixing_stats
|
||||
WHERE date_utc LIKE '%June%'
|
||||
GROUP BY node_id, new_date;
|
||||
|
||||
UPDATE nym_node_daily_mixing_stats AS m
|
||||
SET
|
||||
total_stake = m.total_stake,
|
||||
packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc),
|
||||
packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc),
|
||||
packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc)
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM tmp_nym_node_stats
|
||||
WHERE node_id = m.node_id
|
||||
AND new_date = m.date_utc
|
||||
);
|
||||
|
||||
DELETE FROM nym_node_daily_mixing_stats
|
||||
WHERE date_utc LIKE '%June%';
|
||||
|
||||
INSERT INTO nym_node_daily_mixing_stats
|
||||
(node_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped)
|
||||
SELECT
|
||||
node_id,
|
||||
new_date,
|
||||
total_stake_sum,
|
||||
packets_received_sum,
|
||||
packets_sent_sum,
|
||||
packets_dropped_sum
|
||||
FROM tmp_nym_node_stats AS t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM nym_node_daily_mixing_stats AS m
|
||||
WHERE m.node_id = t.node_id
|
||||
AND m.date_utc = t.new_date
|
||||
);
|
||||
|
||||
DROP TABLE tmp_nym_node_stats;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
http::{self, models::SummaryHistory},
|
||||
utils::{decimal_to_i64, unix_timestamp_to_utc_rfc3339, NumericalCheckedCast},
|
||||
@@ -14,6 +12,7 @@ use nym_validator_client::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use std::str::FromStr;
|
||||
use strum_macros::{EnumString, FromRepr};
|
||||
use time::{Date, OffsetDateTime};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
@@ -107,10 +107,7 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result<Vec<Gatewa
|
||||
.into_iter()
|
||||
.map(|item| item.try_into())
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.map_err(|e| {
|
||||
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
|
||||
e
|
||||
})?;
|
||||
.inspect_err(|e| error!("Conversion from DTO failed: {e}. Invalidly stored data?"))?;
|
||||
tracing::trace!("Fetched {} gateways from DB", items.len());
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<NymNo
|
||||
bond_info as "bond_info: serde_json::Value"
|
||||
FROM
|
||||
nym_nodes
|
||||
ORDER BY
|
||||
node_id
|
||||
"#,
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json, Router,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::{api::dvpn::MIN_SUPPORTED_VERSION, models::DVpnGateway};
|
||||
use crate::http::{error::HttpResult, state::AppState};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/country/:two_letter_country_code",
|
||||
axum::routing::get(get_gateways_by_country),
|
||||
)
|
||||
.route("/countries", axum::routing::get(get_gateway_countries))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // clippy doesn't detect usage in utoipa macros
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
pub(crate) struct TwoLetterCountryCodeParam {
|
||||
#[param(min_length = 2, max_length = 2)]
|
||||
#[param(value_type = String)]
|
||||
#[serde(rename = "two_letter_country_code")]
|
||||
pub(crate) country: celes::Country,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
params(
|
||||
TwoLetterCountryCodeParam
|
||||
),
|
||||
path = "/country/{two_letter_country_code}",
|
||||
summary = "Gets available gateways from the Nym network directory by country",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getGatewaysByCountry",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state), fields(two_letter_country_code = country.alpha2))]
|
||||
pub async fn get_gateways_by_country(
|
||||
Path(TwoLetterCountryCodeParam { country }): Path<TwoLetterCountryCodeParam>,
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|gw| gw.location.two_letter_iso_country_code.to_uppercase() == country.alpha2)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/countries",
|
||||
summary = "Gets available exit gateway countries as two-letter ISO country codes from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getGatewayCountries",
|
||||
responses(
|
||||
(status = 200, body = Vec<String>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn get_gateway_countries(state: State<AppState>) -> HttpResult<Json<Vec<String>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|gw| gw.location.two_letter_iso_country_code.to_string())
|
||||
// dedup relies on iterator being sorted by country, but we already do that
|
||||
.dedup()
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::http::{
|
||||
api::dvpn::{country::TwoLetterCountryCodeParam, MIN_SUPPORTED_VERSION},
|
||||
models::DVpnGateway,
|
||||
};
|
||||
use crate::http::{error::HttpResult, state::AppState};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json, Router,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use tracing::instrument;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/entry", axum::routing::get(get_entry_gateways))
|
||||
.route(
|
||||
"/entry/countries",
|
||||
axum::routing::get(get_entry_gateway_countries),
|
||||
)
|
||||
.route(
|
||||
"/entry/country/:two_letter_country_code",
|
||||
axum::routing::get(get_entry_gateways_by_country),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/entry",
|
||||
summary = "Gets available entry gateways from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getEntryGateways",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn get_entry_gateways(state: State<AppState>) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/entry/countries",
|
||||
summary = "Gets available entry gateway countries as two-letter ISO country codes from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getEntryGatewayCountries",
|
||||
responses(
|
||||
(status = 200, body = Vec<String>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn get_entry_gateway_countries(state: State<AppState>) -> HttpResult<Json<Vec<String>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|gw| gw.location.two_letter_iso_country_code.to_string())
|
||||
// dedup relies on iterator being sorted by country, but we already do that
|
||||
.dedup()
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
params(
|
||||
TwoLetterCountryCodeParam
|
||||
),
|
||||
path = "/entry/country/{two_letter_country_code}",
|
||||
summary = "Gets available entry gateways from the Nym network directory by country",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getEntryGatewaysByCountry",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state), fields(two_letter_country_code = country.alpha2))]
|
||||
pub async fn get_entry_gateways_by_country(
|
||||
Path(TwoLetterCountryCodeParam { country }): Path<TwoLetterCountryCodeParam>,
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|gw| gw.location.two_letter_iso_country_code.to_uppercase() == country.alpha2)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::http::{
|
||||
api::dvpn::{country::TwoLetterCountryCodeParam, MIN_SUPPORTED_VERSION},
|
||||
models::DVpnGateway,
|
||||
};
|
||||
use crate::http::{error::HttpResult, state::AppState};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json, Router,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use tracing::instrument;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/exit", axum::routing::get(get_exit_gateways))
|
||||
.route(
|
||||
"/exit/countries",
|
||||
axum::routing::get(get_entry_gateway_countries),
|
||||
)
|
||||
.route(
|
||||
"/exit/country/:two_letter_country_code",
|
||||
axum::routing::get(get_exit_gateways_by_country),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/exit",
|
||||
summary = "Gets available exit gateways from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getExitGateways",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn get_exit_gateways(state: State<AppState>) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/exit/countries",
|
||||
summary = "Gets available exit gateway countries as two-letter ISO country codes from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getExitGatewayCountries",
|
||||
responses(
|
||||
(status = 200, body = Vec<String>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn get_entry_gateway_countries(state: State<AppState>) -> HttpResult<Json<Vec<String>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|gw| gw.location.two_letter_iso_country_code.to_string())
|
||||
// dedup relies on iterator being sorted by country, but we already do that
|
||||
.dedup()
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
params(
|
||||
TwoLetterCountryCodeParam
|
||||
),
|
||||
path = "/exit/country/{two_letter_country_code}",
|
||||
summary = "Gets available exit gateways from the Nym network directory by country",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getExitGatewaysByCountry",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state), fields(two_letter_country_code = country.alpha2))]
|
||||
pub async fn get_exit_gateways_by_country(
|
||||
Path(TwoLetterCountryCodeParam { country }): Path<TwoLetterCountryCodeParam>,
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|gw| gw.location.two_letter_iso_country_code.to_uppercase() == country.alpha2)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use crate::http::state::AppState;
|
||||
use axum::Router;
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
Json,
|
||||
};
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use std::sync::LazyLock;
|
||||
use tracing::instrument;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::error::{HttpError, HttpResult};
|
||||
use crate::http::models::DVpnGateway;
|
||||
|
||||
pub mod country;
|
||||
pub mod entry;
|
||||
pub mod exit;
|
||||
|
||||
static MIN_SUPPORTED_VERSION: LazyLock<Version> = LazyLock::new(|| Version::new(1, 6, 2));
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", axum::routing::get(dvpn_gateways))
|
||||
.merge(country::routes())
|
||||
.merge(entry::routes())
|
||||
.merge(exit::routes())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
pub(crate) struct MinNodeVersionQuery {
|
||||
min_node_version: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
params(
|
||||
MinNodeVersionQuery
|
||||
),
|
||||
path = "",
|
||||
summary = "Gets available entry and exit gateways from the Nym network directory",
|
||||
context_path = "/dvpn/v1/directory/gateways",
|
||||
operation_id = "getGateways",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state))]
|
||||
pub async fn dvpn_gateways(
|
||||
Query(MinNodeVersionQuery { min_node_version }): Query<MinNodeVersionQuery>,
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
let min_node_version = match min_node_version {
|
||||
Some(min_version) => semver::Version::parse(&min_version)
|
||||
.map_err(|_| HttpError::invalid_input("Min version must be valid semver"))?,
|
||||
None => MIN_SUPPORTED_VERSION.clone(),
|
||||
};
|
||||
|
||||
Ok(Json(
|
||||
state
|
||||
.cache()
|
||||
.get_dvpn_gateway_list(state.db_pool(), &min_node_version)
|
||||
.await,
|
||||
))
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::http::{server::HttpServer, state::AppState};
|
||||
|
||||
pub(crate) mod dvpn;
|
||||
pub(crate) mod gateways;
|
||||
pub(crate) mod metrics;
|
||||
pub(crate) mod mixnodes;
|
||||
@@ -44,6 +45,10 @@ impl RouterBuilder {
|
||||
"/explorer/v3",
|
||||
Router::new().nest("/nym-nodes", nym_nodes::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/dvpn/v1",
|
||||
Router::new().nest("/directory/gateways", dvpn::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/internal",
|
||||
Router::new().nest("/testruns", testruns::routes()),
|
||||
|
||||
@@ -11,14 +11,14 @@ pub(crate) struct HttpError {
|
||||
impl HttpError {
|
||||
pub(crate) fn invalid_input(msg: impl Display) -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": msg.to_string()}).to_string(),
|
||||
message: msg.to_string(),
|
||||
status: axum::http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unauthorized() -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": "Make sure your public key is registered with NS API"}).to_string(),
|
||||
message: String::from("Make sure your public key is registered with NS API"),
|
||||
status: axum::http::StatusCode::UNAUTHORIZED,
|
||||
}
|
||||
}
|
||||
@@ -30,22 +30,21 @@ impl HttpError {
|
||||
|
||||
pub(crate) fn internal() -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": "Internal server error"}).to_string(),
|
||||
message: String::from("Internal server error"),
|
||||
status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn no_testruns_available() -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": "No testruns available"}).to_string(),
|
||||
message: String::from("No testruns available"),
|
||||
status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn no_delegations_for_node(node_id: NodeId) -> Self {
|
||||
Self {
|
||||
message: serde_json::json!({"message": format!("No delegation data for node_id={}", node_id)})
|
||||
.to_string(),
|
||||
message: format!("No delegation data for node_id={}", node_id),
|
||||
status: axum::http::StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::monitor::ExplorerPrettyBond;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||
use nym_mixnet_contract_common::CoinSchema;
|
||||
use nym_node_requests::api::v1::node::models::NodeDescription;
|
||||
use nym_validator_client::client::NodeId;
|
||||
use nym_validator_client::{
|
||||
client::NodeId,
|
||||
models::{
|
||||
AuthenticatorDetails, BinaryBuildInformationOwned, IpPacketRouterDetails, NymNodeData,
|
||||
},
|
||||
nym_api::SkimmedNode,
|
||||
nym_nodes::{BasicEntryInformation, NodeRole},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, instrument};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub(crate) use nym_node_status_client::models::TestrunAssignment;
|
||||
@@ -23,6 +34,220 @@ pub struct Gateway {
|
||||
pub config_score: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct BuildInformation {
|
||||
pub build_version: String,
|
||||
pub commit_branch: String,
|
||||
pub commit_sha: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct Location {
|
||||
pub two_letter_iso_country_code: String,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct DVpnGateway {
|
||||
pub identity_key: String,
|
||||
pub name: String,
|
||||
pub ip_packet_router: Option<IpPacketRouterDetails>,
|
||||
pub authenticator: Option<AuthenticatorDetails>,
|
||||
pub location: Location,
|
||||
pub last_probe: Option<DirectoryGwProbe>,
|
||||
#[schema(value_type = Vec<String>)]
|
||||
pub ip_addresses: Vec<IpAddr>,
|
||||
pub mix_port: u16,
|
||||
pub role: NodeRole,
|
||||
pub entry: Option<BasicEntryInformation>,
|
||||
// The performance data here originates from the nym-api, and is effectively mixnet performance
|
||||
// at the time of writing this
|
||||
pub performance: String,
|
||||
pub build_information: BinaryBuildInformationOwned,
|
||||
}
|
||||
|
||||
impl DVpnGateway {
|
||||
pub fn can_route_entry(&self) -> bool {
|
||||
self.last_probe
|
||||
.as_ref()
|
||||
.map(|probe| match &probe.outcome.as_entry {
|
||||
directory_gw_probe_outcome::Entry::Tested(entry_test_result) => {
|
||||
entry_test_result.can_route
|
||||
}
|
||||
directory_gw_probe_outcome::Entry::NotTested
|
||||
| directory_gw_probe_outcome::Entry::EntryFailure => false,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn can_route_exit(&self) -> bool {
|
||||
self.last_probe
|
||||
.as_ref()
|
||||
.map(|probe| {
|
||||
probe
|
||||
.outcome
|
||||
.as_exit
|
||||
.as_ref()
|
||||
.map(|outcome| {
|
||||
outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// based on
|
||||
/// https://github.com/nymtech/nym-vpn-client/blob/nym-vpn-core-v1.10.0/nym-vpn-core/crates/nym-gateway-probe/src/types.rs
|
||||
/// TODO: long term types should be moved into this repo because nym-vpn-client
|
||||
/// could pull it as a dependency and we'd have a single source of truth
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct LastProbeResult {
|
||||
node: String,
|
||||
used_entry: String,
|
||||
outcome: ProbeOutcome,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct DirectoryGwProbe {
|
||||
last_updated_utc: String,
|
||||
outcome: ProbeOutcome,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ProbeOutcome {
|
||||
as_entry: directory_gw_probe_outcome::Entry,
|
||||
as_exit: Option<directory_gw_probe_outcome::Exit>,
|
||||
wg: Option<wg_outcome_versions::ProbeOutcomeV1>,
|
||||
}
|
||||
|
||||
pub mod directory_gw_probe_outcome {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Entry {
|
||||
Tested(EntryTestResult),
|
||||
NotTested,
|
||||
EntryFailure,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct EntryTestResult {
|
||||
pub can_connect: bool,
|
||||
pub can_route: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct Exit {
|
||||
pub can_connect: bool,
|
||||
pub can_route_ip_v4: bool,
|
||||
pub can_route_ip_external_v4: bool,
|
||||
pub can_route_ip_v6: bool,
|
||||
pub can_route_ip_external_v6: bool,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod wg_outcome_versions {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ProbeOutcomeV1 {
|
||||
pub can_register: bool,
|
||||
|
||||
pub can_handshake_v4: bool,
|
||||
pub can_resolve_dns_v4: bool,
|
||||
pub ping_hosts_performance_v4: f32,
|
||||
pub ping_ips_performance_v4: f32,
|
||||
|
||||
pub can_handshake_v6: bool,
|
||||
pub can_resolve_dns_v6: bool,
|
||||
pub ping_hosts_performance_v6: f32,
|
||||
pub ping_ips_performance_v6: f32,
|
||||
|
||||
pub download_duration_sec_v4: u64,
|
||||
pub downloaded_file_v4: String,
|
||||
pub download_error_v4: String,
|
||||
|
||||
pub download_duration_sec_v6: u64,
|
||||
pub downloaded_file_v6: String,
|
||||
pub download_error_v6: String,
|
||||
}
|
||||
}
|
||||
|
||||
impl DVpnGateway {
|
||||
#[instrument(level = tracing::Level::INFO, name = "dvpn_gw_new", skip_all, fields(gateway_key = gateway.gateway_identity_key, node_id = skimmed_node.node_id))]
|
||||
pub(crate) fn new(gateway: Gateway, skimmed_node: &SkimmedNode) -> anyhow::Result<Self> {
|
||||
let location = gateway
|
||||
.explorer_pretty_bond
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing explorer_pretty_bond"))
|
||||
.and_then(|value| {
|
||||
serde_json::from_value::<ExplorerPrettyBond>(value).map_err(From::from)
|
||||
})
|
||||
.map(|bond| bond.location)?;
|
||||
|
||||
let self_described = gateway
|
||||
.self_described
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing self_described"))
|
||||
.and_then(|value| serde_json::from_value::<NymNodeData>(value).map_err(From::from))?;
|
||||
|
||||
let last_probe_result = match gateway.last_probe_result {
|
||||
Some(value) => {
|
||||
let parsed =
|
||||
serde_json::from_value::<LastProbeResult>(value).inspect_err(|err| {
|
||||
error!("Failed to deserialize probe result: {err}");
|
||||
})?;
|
||||
Some(parsed)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
identity_key: gateway.gateway_identity_key,
|
||||
name: gateway.description.moniker,
|
||||
ip_packet_router: self_described.ip_packet_router,
|
||||
authenticator: self_described.authenticator,
|
||||
location: Location {
|
||||
latitude: location.location.latitude,
|
||||
longitude: location.location.longitude,
|
||||
two_letter_iso_country_code: location.two_letter_iso_country_code,
|
||||
},
|
||||
last_probe: last_probe_result.map(|res| DirectoryGwProbe {
|
||||
last_updated_utc: gateway.last_testrun_utc.unwrap_or_default(),
|
||||
outcome: res.outcome,
|
||||
}),
|
||||
ip_addresses: skimmed_node.ip_addresses.clone(),
|
||||
mix_port: skimmed_node.mix_port,
|
||||
role: skimmed_node.role.clone(),
|
||||
entry: skimmed_node.entry.clone(),
|
||||
performance: to_percent(gateway.performance),
|
||||
build_information: self_described.build_information,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_percent(performance: u8) -> String {
|
||||
let fraction = performance as f32 / 100.0;
|
||||
format!("{:.2}", fraction)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_percent_should_work() {
|
||||
let starting = [0u8, 33, 50, 99, 100];
|
||||
let expected = ["0.00", "0.33", "0.50", "0.99", "1.00"];
|
||||
|
||||
for (starting, expected) in starting.into_iter().zip(expected) {
|
||||
assert_eq!(expected, to_percent(starting));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct GatewaySkinny {
|
||||
pub gateway_identity_key: String,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use cosmwasm_std::Decimal;
|
||||
use itertools::Itertools;
|
||||
use moka::{future::Cache, Entry};
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::nym_api::SkimmedNode;
|
||||
use semver::Version;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
use tracing::{error, instrument, warn};
|
||||
|
||||
use crate::{
|
||||
db::{queries, DbPool},
|
||||
http::models::{DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory},
|
||||
http::models::{
|
||||
DVpnGateway, DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory,
|
||||
},
|
||||
monitor::{DelegationsCache, NodeGeoCache},
|
||||
};
|
||||
|
||||
@@ -77,6 +81,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
static GATEWAYS_LIST_KEY: &str = "gateways";
|
||||
static DVPN_GATEWAYS_LIST_KEY: &str = "dvpn_gateways";
|
||||
static MIXNODES_LIST_KEY: &str = "mixnodes";
|
||||
static NYM_NODES_LIST_KEY: &str = "nym_nodes";
|
||||
static MIXSTATS_LIST_KEY: &str = "mixstats";
|
||||
@@ -88,6 +93,7 @@ const MIXNODE_STATS_HISTORY_DAYS: usize = 30;
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HttpCache {
|
||||
gateways: Cache<String, Arc<RwLock<Vec<Gateway>>>>,
|
||||
dvpn_gateways: Cache<String, Arc<RwLock<Vec<DVpnGateway>>>>,
|
||||
mixnodes: Cache<String, Arc<RwLock<Vec<Mixnode>>>>,
|
||||
nym_nodes: Cache<String, Arc<RwLock<Vec<ExtendedNymNode>>>>,
|
||||
mixstats: Cache<String, Arc<RwLock<Vec<DailyStats>>>>,
|
||||
@@ -102,6 +108,10 @@ impl HttpCache {
|
||||
.max_capacity(2)
|
||||
.time_to_live(Duration::from_secs(ttl_seconds))
|
||||
.build(),
|
||||
dvpn_gateways: Cache::builder()
|
||||
.max_capacity(6)
|
||||
.time_to_live(Duration::from_secs(ttl_seconds))
|
||||
.build(),
|
||||
mixnodes: Cache::builder()
|
||||
.max_capacity(2)
|
||||
.time_to_live(Duration::from_secs(ttl_seconds))
|
||||
@@ -158,10 +168,11 @@ impl HttpCache {
|
||||
let gateways = crate::db::queries::get_all_gateways(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
self.upsert_gateway_list(gateways.clone()).await;
|
||||
|
||||
if gateways.is_empty() {
|
||||
tracing::warn!("Database contains 0 gateways");
|
||||
tracing::warn!("Database: gateway list is empty");
|
||||
} else {
|
||||
self.upsert_gateway_list(gateways.clone()).await;
|
||||
}
|
||||
|
||||
gateways
|
||||
@@ -169,6 +180,160 @@ impl HttpCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upsert_dvpn_gateway_list(
|
||||
&self,
|
||||
new_gateway_list: Vec<DVpnGateway>,
|
||||
) -> Entry<String, Arc<RwLock<Vec<DVpnGateway>>>> {
|
||||
self.dvpn_gateways
|
||||
.entry_by_ref(DVPN_GATEWAYS_LIST_KEY)
|
||||
.and_upsert_with(|maybe_entry| async {
|
||||
if let Some(entry) = maybe_entry {
|
||||
let v = entry.into_value();
|
||||
let mut guard = v.write().await;
|
||||
*guard = new_gateway_list;
|
||||
v.clone()
|
||||
} else {
|
||||
Arc::new(RwLock::new(new_gateway_list))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_dvpn_gateway_list(
|
||||
&self,
|
||||
db: &DbPool,
|
||||
min_node_version: &Version,
|
||||
) -> Vec<DVpnGateway> {
|
||||
match self.dvpn_gateways.get(DVPN_GATEWAYS_LIST_KEY).await {
|
||||
Some(guard) => {
|
||||
tracing::trace!("Fetching from cache...");
|
||||
let read_lock = guard.read().await;
|
||||
read_lock.clone()
|
||||
}
|
||||
None => {
|
||||
tracing::trace!("No gateways (dVPN) in cache, refreshing from DB...");
|
||||
|
||||
let gateways = self.get_gateway_list(db).await;
|
||||
|
||||
let started_with = gateways.len();
|
||||
let Ok(skimmed_nodes) = crate::db::queries::get_described_bonded_nym_nodes(db)
|
||||
.await
|
||||
.map(|records| {
|
||||
records
|
||||
.into_iter()
|
||||
.filter_map(|dto| {
|
||||
SkimmedNode::try_from(dto)
|
||||
.inspect_err(|err| {
|
||||
error!("Failed to read SkimmedNode from DB: {err}")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.map(|skimmed_node| {
|
||||
(
|
||||
skimmed_node.ed25519_identity_pubkey.to_base58_string(),
|
||||
skimmed_node,
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
})
|
||||
.inspect_err(|err| {
|
||||
// this would fail only in case of internal error: log and return gracefully
|
||||
error!("Failed to get nym_nodes from DB: {err}");
|
||||
})
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let res_gws = gateways
|
||||
.into_iter()
|
||||
.filter(|gw| gw.bonded)
|
||||
.filter_map(|gw| match skimmed_nodes.get(&gw.gateway_identity_key) {
|
||||
Some(skimmed_node) => Some((gw, skimmed_node)),
|
||||
None => {
|
||||
warn!(
|
||||
"No SkimmedNode data found for GW, identity_key={}",
|
||||
gw.gateway_identity_key
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(
|
||||
|(gw, skimmed_node)| match DVpnGateway::new(gw, skimmed_node) {
|
||||
Ok(gw) => Some(gw),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to convert GW node_id={} to dVPN form: {}",
|
||||
skimmed_node.node_id, err
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.filter(|gw| {
|
||||
let gw_version = &gw.build_information.build_version;
|
||||
if let Ok(gw_version) = Version::parse(gw_version) {
|
||||
&gw_version >= min_node_version
|
||||
} else {
|
||||
warn!("Failed to parse GW version {}", gw_version);
|
||||
false
|
||||
}
|
||||
})
|
||||
.filter(|gw| {
|
||||
// gateways must have a country
|
||||
if gw.location.two_letter_iso_country_code.len() == 2 {
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid country code: {}",
|
||||
gw.location.two_letter_iso_country_code
|
||||
);
|
||||
false
|
||||
}
|
||||
})
|
||||
// sort by country, then by identity key
|
||||
.sorted_by_key(|item| {
|
||||
(
|
||||
item.location.two_letter_iso_country_code.clone(),
|
||||
item.identity_key.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if res_gws.is_empty() && started_with > 0 {
|
||||
tracing::warn!("Started with {}, got 0 gateways", started_with);
|
||||
} else {
|
||||
self.upsert_dvpn_gateway_list(res_gws.clone()).await;
|
||||
}
|
||||
|
||||
res_gws
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_entry_dvpn_gateways(
|
||||
&self,
|
||||
db: &DbPool,
|
||||
min_node_version: &Version,
|
||||
) -> Vec<DVpnGateway> {
|
||||
self.get_dvpn_gateway_list(db, min_node_version)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(DVpnGateway::can_route_entry)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn get_exit_dvpn_gateways(
|
||||
&self,
|
||||
db: &DbPool,
|
||||
min_node_version: &Version,
|
||||
) -> Vec<DVpnGateway> {
|
||||
self.get_dvpn_gateway_list(db, min_node_version)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(DVpnGateway::can_route_exit)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn upsert_mixnode_list(
|
||||
&self,
|
||||
new_mixnode_list: Vec<Mixnode>,
|
||||
@@ -201,10 +366,11 @@ impl HttpCache {
|
||||
let mixnodes = crate::db::queries::get_all_mixnodes(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
self.upsert_mixnode_list(mixnodes.clone()).await;
|
||||
|
||||
if mixnodes.is_empty() {
|
||||
tracing::warn!("Database contains 0 mixnodes");
|
||||
} else {
|
||||
self.upsert_mixnode_list(mixnodes.clone()).await;
|
||||
}
|
||||
|
||||
mixnodes
|
||||
@@ -245,14 +411,13 @@ impl HttpCache {
|
||||
None => {
|
||||
tracing::trace!("No nym nodes in cache, refreshing cache from DB...");
|
||||
|
||||
let nym_nodes = aggregate_node_info_from_db(db, node_geocache)
|
||||
.await
|
||||
.inspect(|nym_nodes| {
|
||||
if nym_nodes.is_empty() {
|
||||
tracing::warn!("Database contains 0 nym nodes");
|
||||
}
|
||||
})?;
|
||||
self.upsert_nym_node_list(nym_nodes.clone()).await;
|
||||
let nym_nodes = aggregate_node_info_from_db(db, node_geocache).await?;
|
||||
|
||||
if nym_nodes.is_empty() {
|
||||
tracing::warn!("Database contains 0 nym nodes");
|
||||
} else {
|
||||
self.upsert_nym_node_list(nym_nodes.clone()).await;
|
||||
}
|
||||
|
||||
Ok(nym_nodes)
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ pub async fn update_daily_stats(
|
||||
let date_utc = format!(
|
||||
"{:04}-{:02}-{:02}",
|
||||
timestamp.year(),
|
||||
timestamp.month(),
|
||||
timestamp.month() as u8,
|
||||
timestamp.day()
|
||||
);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ impl IpInfoClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct ExplorerPrettyBond {
|
||||
pub(crate) identity_key: String,
|
||||
pub(crate) owner: Addr,
|
||||
@@ -71,7 +71,7 @@ pub(crate) struct ExplorerPrettyBond {
|
||||
pub(crate) location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub(crate) struct Location {
|
||||
pub(crate) two_letter_iso_country_code: String,
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::db::models::{
|
||||
NYMNODES_DESCRIBED_COUNT, NYMNODE_COUNT,
|
||||
};
|
||||
use crate::db::{queries, DbPool};
|
||||
use crate::monitor::geodata::{ExplorerPrettyBond, Location};
|
||||
use crate::utils::now_utc;
|
||||
use crate::utils::{decimal_to_i64, LogError, NumericalCheckedCast};
|
||||
use anyhow::anyhow;
|
||||
@@ -30,7 +29,7 @@ use std::{
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
use tracing::instrument;
|
||||
|
||||
pub(crate) use geodata::IpInfoClient;
|
||||
pub(crate) use geodata::{ExplorerPrettyBond, IpInfoClient, Location};
|
||||
pub(crate) use node_delegations::DelegationsCache;
|
||||
|
||||
mod geodata;
|
||||
@@ -106,11 +105,10 @@ impl Monitor {
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api =
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
@@ -134,6 +132,8 @@ impl Monitor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
|
||||
|
||||
let bonded_nym_nodes = api_client
|
||||
.get_all_bonded_nym_nodes()
|
||||
.await?
|
||||
@@ -150,7 +150,8 @@ impl Monitor {
|
||||
.await
|
||||
.log_error("get_all_basic_nodes")?;
|
||||
|
||||
tracing::info!("🟣 get_all_basic_nodes: {}", nym_nodes.len());
|
||||
let nym_node_count = nym_nodes.len();
|
||||
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
|
||||
|
||||
let nym_node_records =
|
||||
self.prepare_nym_node_data(nym_nodes.clone(), &bonded_nym_nodes, &described_nodes);
|
||||
@@ -251,7 +252,7 @@ impl Monitor {
|
||||
//
|
||||
|
||||
let nodes_summary = vec![
|
||||
(NYMNODE_COUNT, nym_nodes.len()),
|
||||
(NYMNODE_COUNT, nym_node_count),
|
||||
(ASSIGNED_MIXING_COUNT, assigned_mixing_count),
|
||||
(MIXNODES_LEGACY_COUNT, count_legacy_mixnodes),
|
||||
(NYMNODES_DESCRIBED_COUNT, described_nodes.len()),
|
||||
@@ -267,7 +268,7 @@ impl Monitor {
|
||||
let last_updated = now_utc();
|
||||
let last_updated_utc = last_updated.unix_timestamp().to_string();
|
||||
let network_summary = NetworkSummary {
|
||||
total_nodes: nym_nodes.len().cast_checked()?,
|
||||
total_nodes: nym_node_count.cast_checked()?,
|
||||
mixnodes: mixnode::MixnodeSummary {
|
||||
bonded: mixnode::MixingNodesSummary {
|
||||
count: assigned_mixing_count.cast_checked()?,
|
||||
@@ -457,11 +458,7 @@ impl Monitor {
|
||||
async fn check_ipinfo_bandwidth(&self) {
|
||||
match self.ipinfo.check_remaining_bandwidth().await {
|
||||
Ok(bandwidth) => {
|
||||
tracing::info!(
|
||||
"ipinfo monthly bandwidth: {}/{} spent",
|
||||
bandwidth.month,
|
||||
bandwidth.limit
|
||||
);
|
||||
tracing::info!("ipinfo monthly bandwidth: {} spent", bandwidth.month);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!("Couldn't check ipinfo bandwidth: {}", err);
|
||||
|
||||
@@ -57,7 +57,7 @@ async fn run(
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
|
||||
.no_hickory_dns()
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
@@ -24,7 +24,7 @@ pub(crate) async fn spawn(pool: DbPool, refresh_interval: Duration) {
|
||||
});
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "testrun_queue", skip_all)]
|
||||
#[instrument(level = "info", name = "testrun_queue", skip_all)]
|
||||
async fn run(pool: &DbPool) -> anyhow::Result<()> {
|
||||
tracing::info!("Spawning testruns...");
|
||||
if pool.is_closed() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.13.0"
|
||||
version = "1.14.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -580,6 +580,13 @@ pub struct MixnetDebug {
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Specify whether any framed packets between nodes should use the legacy format (v7)
|
||||
/// as opposed to the current (v8) one.
|
||||
/// The legacy format has to be used until sufficient number of nodes on the network has upgraded and understand the new variant.
|
||||
/// This will allow for optimisations to indicate which [sphinx] key is meant to be used when
|
||||
/// processing received packets.
|
||||
pub use_legacy_packet_encoding: bool,
|
||||
|
||||
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
|
||||
pub unsafe_disable_noise: bool,
|
||||
}
|
||||
@@ -765,6 +772,8 @@ impl Default for MixnetDebug {
|
||||
packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
// TODO: update this in few releases...
|
||||
use_legacy_packet_encoding: true,
|
||||
unsafe_disable_noise: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,6 +1417,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
|
||||
initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size,
|
||||
unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
storage_paths: NymNodePaths {
|
||||
|
||||
@@ -1057,6 +1057,7 @@ impl NymNode {
|
||||
self.config.mixnet.debug.packet_forwarding_maximum_backoff,
|
||||
self.config.mixnet.debug.initial_connection_timeout,
|
||||
self.config.mixnet.debug.maximum_connection_buffer_size,
|
||||
self.config.mixnet.debug.use_legacy_packet_encoding,
|
||||
);
|
||||
let mixnet_client = nym_mixnet_client::Client::new(
|
||||
mixnet_client_config,
|
||||
|
||||
@@ -270,6 +270,7 @@ impl ThroughputTestingClient {
|
||||
forward_packet,
|
||||
Default::default(),
|
||||
self.key_rotation,
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -441,7 +441,6 @@ impl OptionalValidators {
|
||||
match network {
|
||||
WalletNetwork::MAINNET => self.mainnet.as_ref(),
|
||||
WalletNetwork::SANDBOX => self.sandbox.as_ref(),
|
||||
WalletNetwork::QA => self.qa.as_ref(),
|
||||
}
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -460,12 +459,7 @@ impl fmt::Display for OptionalValidators {
|
||||
.as_ref()
|
||||
.map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
let s3 = self
|
||||
.qa
|
||||
.as_ref()
|
||||
.map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{s1}{s2}{s3}")
|
||||
write!(f, "{s1}{s2}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { Results } from 'src/components/TestNode/Results';
|
||||
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
||||
import { PrintResults } from 'src/components/TestNode/PrintResults';
|
||||
import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants';
|
||||
import { MAINNET_VALIDATOR_URL } from 'src/constants';
|
||||
import { TestStatus } from 'src/components/TestNode/types';
|
||||
import { isMixnode } from 'src/types';
|
||||
|
||||
@@ -63,7 +63,7 @@ export const NodeTestPage = () => {
|
||||
const loadNodeTestClient = useCallback(async () => {
|
||||
try {
|
||||
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
|
||||
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : QA_VALIDATOR_URL;
|
||||
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : 'https://rpc.nymtech.net/api/';
|
||||
const client = await createNodeTesterClient();
|
||||
await client.tester.init(validator, nodeTesterId);
|
||||
setNodeTestClient(client);
|
||||
|
||||
@@ -48,7 +48,7 @@ impl MyTopologyProvider {
|
||||
|
||||
let gateways = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes_v2()
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await
|
||||
.unwrap()
|
||||
.nodes;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.58"
|
||||
version = "1.1.59"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.70"
|
||||
|
||||
@@ -443,7 +443,7 @@ impl NetworkManager {
|
||||
));
|
||||
let id = ctx.nym_node_id(mixnode);
|
||||
cmds.push(format!(
|
||||
"{bin_canon_display} -c {env_canon_display} run --id {id} --local"
|
||||
"{bin_canon_display} -c {env_canon_display} run --id {id} --local --unsafe-disable-noise"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ impl NetworkManager {
|
||||
));
|
||||
let id = ctx.nym_node_id(gateway);
|
||||
cmds.push(format!(
|
||||
"{bin_canon_display} -c {env_canon_display} run --id {id} --local"
|
||||
"{bin_canon_display} -c {env_canon_display} run --id {id} --local --unsafe-disable-noise"
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.57"
|
||||
version = "1.1.58"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nymvisor"
|
||||
version = "0.1.22"
|
||||
version = "0.1.23"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||