diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 4eb9a4b..a20adce 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -3,38 +3,81 @@ on: push: branches: - main + pull_request: + +# Least privilege at the top; the pages job re-grants what it needs. permissions: - contents: write - id-token: write - pages: write + contents: read + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: '3.12' - run: pip install mkdocs-material mkdocs-static-i18n - - run: mkdocs build - - run: cd /home/runner/work/docs/docs/site && tar --dereference -cvf "/home/runner/work/_temp/artifact.tar" . + # --strict fails the build on broken internal links. The docs use + # relative .md cross-links, so this is the gate that keeps them honest. + - run: mkdocs build --strict + # tar --dereference is what preserves the hidden .well-known directory. + # Both deploy jobs consume this one artifact, so the two origins cannot drift. + - name: Package site + run: | + mkdir -p /tmp/pages + tar --dereference -cf /tmp/pages/artifact.tar -C site . - uses: actions/upload-artifact@v4 with: name: github-pages - path: /home/runner/work/_temp/artifact.tar + path: /tmp/pages/artifact.tar retention-days: 1 if-no-files-found: error - compression-level: 6 - overwrite: false - include-hidden-files: false - - uses: actions/deploy-pages@v4 + + deploy-pages: + needs: build + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 with: - timeout: 600000 - error_count: 10 - reporting_interval: 5000 artifact_name: github-pages - preview: false + + deploy-vercel: + needs: build + # Fork PRs cannot receive secrets, so skip rather than fail. They still get + # the build job above as validation. pull_request_target is deliberately + # NOT used: it would expose the Vercel token to untrusted PR code. + if: >- + github.event_name == 'push' || + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: github-pages + path: /tmp/pages + - run: npm install --global vercel@latest + - name: Package for Vercel + run: scripts/build-vercel-output.sh /tmp/pages/artifact.tar + - name: Deploy (production) + if: github.event_name == 'push' + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy (preview) + if: github.event_name == 'pull_request' + run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.gitignore b/.gitignore index 8bfa453..e5dc41b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,13 @@ dist/ .devcontainer/ smartcontract/.vscode/settings.json site/ +.venv/ +.vercel/ .DS_Store **/.DS_Store __pycache__/ **/__pycache__/ +.vercel +.env* diff --git a/docs/Validator Multicast Connection.es.md b/docs/Validator Multicast Connection.es.md index 36b4658..1919fac 100644 --- a/docs/Validator Multicast Connection.es.md +++ b/docs/Validator Multicast Connection.es.md @@ -1,14 +1,16 @@ -# Conexión Multicast para Validadores -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Configure un validador conectado para publicar leader shreds en el feed multicast de DoubleZero edge. +--- +# Conexión Multicast del Validador !!! warning "Al conectarme a DoubleZero acepto los [Términos de Servicio de DoubleZero](https://doublezero.xyz/terms-protocol)" -!!! note inline end "Empresas de trading y negocios" - Si opera una empresa de trading o un negocio que desea suscribirse al feed, registre su interés para obtener más información [aquí](https://doublezero.xyz/edge-form). +!!! note inline end "Firmas de trading y empresas" + Si opera una firma de trading o empresa que desea suscribirse al feed, por favor registre su interés para obtener más información [aquí](https://doublezero.xyz/edge-form). -Si aún no está conectado a DoubleZero, complete la documentación de [Configuración](setup.md) y de conexión de validador [Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md). +Si aún no está conectado a DoubleZero, por favor complete la documentación de [Configuración]() y de conexión del validador a [Mainnet-Beta](). -Si es un validador ya conectado a DoubleZero, puede continuar con esta guía. +Si es un validador que ya está conectado a DoubleZero, puede continuar con esta guía. ## 1. Configuración del Cliente @@ -18,7 +20,7 @@ Si es un validador ya conectado a DoubleZero, puede continuar con esta guía. Puede enviar a Jito y al grupo `edge-solana-shreds` al mismo tiempo. - Ejemplo: + ejemplo: ```json #!/bin/bash @@ -31,7 +33,7 @@ Si es un validador ya conectado a DoubleZero, puede continuar con esta guía. ``` 2. Reinicie su validador. -3. Conéctese al grupo de multicast de DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Conéctese al grupo multicast de DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer @@ -43,19 +45,37 @@ Si es un validador ya conectado a DoubleZero, puede continuar con esta guía. ``` 2. Reinicie su validador. -3. Conéctese al grupo de multicast de DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Conéctese al grupo multicast de DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` -## 2. Confirmar que está publicando shreds de líder +## 2. Confirme que está publicando leader shreds -Una vez conectado, puede verificar [este panel](https://data.doublezero.xyz/dz/publisher-check) para confirmar que está publicando shreds. No verá la confirmación hasta que haya publicado shreds de líder para al menos un slot. +Una vez que esté conectado, puede verificar [este panel](https://data.doublezero.xyz/dz/publisher-check) para confirmar que está publicando shreds. No verá la confirmación hasta después de haber publicado leader shreds durante al menos un slot. -## 3. Recompensas para Validadores +## Endpoints Multicast (IP vs Puerto) -Por cada época en que los validadores publiquen shreds de líder, serán recompensados proporcionalmente por su contribución según las suscripciones. Los detalles de este sistema serán anunciados y detallados en una fecha posterior. +Para el tráfico de shreds, la **dirección IP** selecciona el feed multicast y el **puerto** selecciona el servicio UDP. +Todos los feeds a continuación usan el puerto UDP `7733`. + +Puede descubrir las IPs de grupo actuales con: + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds` (leader): `233.84.178.1:7733` +- `edge-solana-retrans-eu`: `233.84.178.12:7733` +- `edge-solana-retrans-apac`: `233.84.178.13:7733` +- `edge-solana-retrans-amer`: `233.84.178.14:7733` + +Para referencias de API y endpoints de datos legibles por máquina, consulte [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs). + +## 3. Recompensas del Validador + +Por cada época en la que los validadores publiquen leader shreds, serán recompensados proporcionalmente por su contribución basándose en las suscripciones. Los detalles específicos de este sistema serán anunciados y detallados en una fecha posterior. ## Solución de Problemas -### No se publican shreds de líder: +### No se Publican Leader Shreds: La causa más común de no transmitir shreds es la versión del cliente: @@ -63,14 +83,15 @@ Debe estar ejecutando Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer o Harmoni ### Retransmisión: -1. Una causa común de retransmisión de shreds es una configuración simple. Es posible que tenga habilitado el flag para enviar shreds de retransmisión en su script de inicio; deberá deshabilitarlo. +1. Una causa común de retransmisión de shreds es una configuración simple. Es posible que tenga habilitada la flag para enviar shreds de retransmisión en su script de inicio; deberá deshabilitarla. + + La flag a eliminar en Jito-Agave es: `--shred-retransmit-receiver-address`. - El flag que debe eliminar en Jito-Agave es: `--shred-retransmit-receiver-address`. +1. Revise el [panel de publicadores](https://data.doublezero.xyz/dz/publisher-check) y vea si tiene shreds retransmitidos. En la tabla, observe la columna **No Retransmit Shreds**—una X roja significa que está retransmitiendo. -1. Revise el [panel de publicadores](https://data.doublezero.xyz/dz/publisher-check) y compruebe si tiene shreds retransmitidos. En la tabla, observe la columna **No Retransmit Shreds**—una X roja significa que está retransmitiendo. + !!! note "vista por época" + Tenga en cuenta que hay diferentes ventanas de tiempo para ver el panel de publicadores. Si ve retransmisión en la **vista de 2 épocas**, pero ha realizado un cambio reciente, intente cambiar a la vista de **slot reciente**. - !!! note "Vista de época" - Tenga en cuenta que hay diferentes ventanas de tiempo para ver el panel de publicadores. Si ve retransmisión en la **vista de 2 épocas**, pero realizó un cambio reciente, intente cambiar a la vista de **slot reciente**. ![Panel de verificación de publicadores](images/publisher-check-dashboard.png) @@ -78,14 +99,14 @@ Debe estar ejecutando Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer o Harmoni ![Usuarios de DoubleZero Data](images/doublezero-data-users.png) -3. Haga clic en **Multicast** para abrir su vista de multicast. +3. Haga clic en **Multicast** para abrir su vista multicast. - La captura de pantalla a continuación muestra: **Retransmitiendo** (indeseable) tráfico saliente constante sin patrón de slot de líder. + La captura de pantalla a continuación muestra: **Retransmitiendo** (no deseado) tráfico saliente constante sin patrón de leader-slot. ![Vista multicast del usuario - ejemplo de retransmisión](images/user-multicast-view-retransmit.png) - La captura de pantalla a continuación muestra: **Saludable** (publicando solo shreds de líder) tráfico saliente en picos, conocido como patrón de diente de sierra, que se alinea con sus slots de líder. + La captura de pantalla a continuación muestra: **Saludable** (publicando solo leader shreds) tráfico saliente en picos, conocido como patrón de diente de sierra, que se alinean con sus leader slots. ![Vista multicast del usuario - ejemplo de publicador saludable](images/user-multicast-view-healthy.png) -El gráfico muestra si está enviando solo shreds de líder. Los picos de tráfico deben alinearse con cuando tiene un slot de líder. Cuando no tiene un slot de líder, no debe haber tráfico. Si está retransmitiendo, verá un flujo constante de tráfico en lugar de picos alineados con slots. +El gráfico muestra si está enviando solo leader shreds. Los picos de tráfico deben alinearse con el momento en que tiene un leader slot. Cuando no tiene un leader slot, no debería haber tráfico. Si está retransmitiendo, verá un flujo constante de tráfico en lugar de picos alineados con los slots. \ No newline at end of file diff --git a/docs/Validator Multicast Connection.fr.md b/docs/Validator Multicast Connection.fr.md index 4ccb741..b43233a 100644 --- a/docs/Validator Multicast Connection.fr.md +++ b/docs/Validator Multicast Connection.fr.md @@ -1,14 +1,16 @@ -# Connexion Multicast Validateur -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Configurer un validateur connecté pour publier les shreds de leader vers le flux multicast edge de DoubleZero. +--- -!!! warning "En me connectant à DoubleZero, j'accepte les [Conditions d'Utilisation de DoubleZero](https://doublezero.xyz/terms-protocol)" +# Connexion Multicast du Validateur +!!! warning "En me connectant à DoubleZero, j'accepte les [Conditions d'utilisation de DoubleZero](https://doublezero.xyz/terms-protocol)" !!! note inline end "Sociétés de trading et entreprises" - Si vous exploitez une société de trading ou une entreprise souhaitant s'abonner au flux, enregistrez votre intérêt pour obtenir plus d'informations [ici](https://doublezero.xyz/edge-form). + Si vous exploitez une société de trading ou une entreprise souhaitant s'abonner au flux, veuillez manifester votre intérêt pour obtenir plus d'informations [ici](https://doublezero.xyz/edge-form). -Si vous n'êtes pas encore connecté à DoubleZero, veuillez d'abord compléter la documentation de [Configuration](setup.md) et de connexion validateur [Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md). +Si vous n'êtes pas encore connecté à DoubleZero, veuillez d'abord compléter la documentation de [Configuration]() et de connexion du validateur au [Mainnet-Beta](). -Si vous êtes un validateur déjà connecté à DoubleZero, vous pouvez continuer ce guide. +Si vous êtes un validateur déjà connecté à DoubleZero, vous pouvez poursuivre ce guide. ## 1. Configuration du Client @@ -18,7 +20,7 @@ Si vous êtes un validateur déjà connecté à DoubleZero, vous pouvez continue Vous pouvez envoyer à Jito et au groupe `edge-solana-shreds` en même temps. - Exemple : + exemple : ```json #!/bin/bash @@ -31,7 +33,7 @@ Si vous êtes un validateur déjà connecté à DoubleZero, vous pouvez continue ``` 2. Redémarrez votre validateur. -3. Connectez-vous au groupe multicast DoubleZero `edge-solana-shreds` en tant que publicateur : `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Connectez-vous au groupe multicast DoubleZero `edge-solana-shreds` en tant qu'éditeur : `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer @@ -43,49 +45,68 @@ Si vous êtes un validateur déjà connecté à DoubleZero, vous pouvez continue ``` 2. Redémarrez votre validateur. -3. Connectez-vous au groupe multicast DoubleZero `edge-solana-shreds` en tant que publicateur : `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Connectez-vous au groupe multicast DoubleZero `edge-solana-shreds` en tant qu'éditeur : `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ## 2. Confirmer que vous publiez des shreds de leader Une fois connecté, vous pouvez vérifier [ce tableau de bord](https://data.doublezero.xyz/dz/publisher-check) pour confirmer que vous publiez des shreds. Vous ne verrez pas de confirmation tant que vous n'aurez pas publié des shreds de leader pour au moins un slot. +## Points de Terminaison Multicast (IP vs Port) + +Pour le trafic de shreds, l'**adresse IP** sélectionne le flux multicast et le **port** sélectionne le service UDP. +Tous les flux ci-dessous utilisent le port UDP `7733`. + +Vous pouvez découvrir les IP de groupe actuelles avec : + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds` (leader) : `233.84.178.1:7733` +- `edge-solana-retrans-eu` : `233.84.178.12:7733` +- `edge-solana-retrans-apac` : `233.84.178.13:7733` +- `edge-solana-retrans-amer` : `233.84.178.14:7733` + +Pour les références API et les points de terminaison de données lisibles par machine, consultez [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs). + ## 3. Récompenses des Validateurs -Pour chaque époque où les validateurs publient des shreds de leader, ils seront récompensés proportionnellement pour leur contribution en fonction des abonnements. Les détails de ce système seront annoncés et détaillés ultérieurement. +Pour chaque époque où les validateurs publient des shreds de leader, ils seront proportionnellement récompensés pour leur contribution en fonction des abonnements. Les détails de ce système seront annoncés et précisés ultérieurement. ## Dépannage -### Pas de publication de shreds de leader : +### Pas de Publication de Shreds de Leader : -La cause la plus fréquente de non-transmission des shreds est la version du client : +La cause la plus courante de non-transmission de shreds est la version du client : -Vous devez utiliser Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer ou Harmonic 3.1.11+. Les autres versions de client ne fonctionneront pas. +Vous devez utiliser Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer, ou Harmonic 3.1.11+. Les autres versions de client ne fonctionneront pas. ### Retransmission : -1. Une cause courante de retransmission de shreds est une configuration simple. Vous avez peut-être activé le flag d'envoi de shreds de retransmission dans votre script de démarrage ; vous devrez le désactiver. +1. Une cause courante de retransmission de shreds est une simple erreur de configuration. Vous avez peut-être activé le flag pour envoyer des shreds de retransmission dans votre script de démarrage ; vous devrez le désactiver. Le flag à supprimer dans Jito-Agave est : `--shred-retransmit-receiver-address`. -1. Vérifiez le [tableau de bord des publicateurs](https://data.doublezero.xyz/dz/publisher-check) et voyez si vous avez des shreds retransmis. Dans le tableau, regardez la colonne **No Retransmit Shreds**—une croix rouge signifie que vous retransmettez. +1. Vérifiez le [tableau de bord des éditeurs](https://data.doublezero.xyz/dz/publisher-check) et voyez si vous avez des shreds retransmis. Dans le tableau, regardez la colonne **No Retransmit Shreds** — un X rouge signifie que vous retransmettez. + + !!! note "vue par époque" + Notez qu'il existe différentes fenêtres temporelles pour visualiser le tableau de bord des éditeurs. Si vous voyez de la retransmission dans la **vue sur 2 époques**, mais que vous avez effectué un changement récent, essayez de passer à la vue **slot récent**. - !!! note "Vue par époque" - Notez qu'il existe différentes fenêtres temporelles pour afficher le tableau de bord des publicateurs. Si vous voyez une retransmission dans la **vue 2 époques**, mais que vous avez effectué un changement récent, essayez de passer à la vue **slot récent**. - ![Tableau de bord de vérification des publicateurs](images/publisher-check-dashboard.png) + ![Tableau de bord de vérification des éditeurs](images/publisher-check-dashboard.png) -2. Trouvez l'IP de votre client et cherchez votre utilisateur dans [DoubleZero Data](https://data.doublezero.xyz/dz/users). +2. Trouvez l'IP de votre client et recherchez votre utilisateur dans [DoubleZero Data](https://data.doublezero.xyz/dz/users). ![Utilisateurs DoubleZero Data](images/doublezero-data-users.png) 3. Cliquez sur **Multicast** pour ouvrir votre vue multicast. - La capture d'écran ci-dessous montre : **Retransmission** (indésirable) un trafic sortant constant sans motif de slot de leader. + La capture d'écran ci-dessous montre : **Retransmission** (indésirable) un trafic sortant constant sans schéma de slots de leader. - ![Vue multicast utilisateur - exemple de retransmission](images/user-multicast-view-retransmit.png) + ![Vue multicast de l'utilisateur - exemple de retransmission](images/user-multicast-view-retransmit.png) - La capture d'écran ci-dessous montre : **Sain** (publication uniquement de shreds de leader) un trafic sortant en pics, connu sous le nom de motif en dents de scie, qui s'aligne sur vos slots de leader. + La capture d'écran ci-dessous montre : **Sain** (publication uniquement de shreds de leader) un trafic sortant en pics, connu sous le nom de motif en dents de scie, qui correspondent à vos slots de leader. - ![Vue multicast utilisateur - exemple de publicateur sain](images/user-multicast-view-healthy.png) + ![Vue multicast de l'utilisateur - exemple d'éditeur sain](images/user-multicast-view-healthy.png) -Le graphique indique si vous envoyez uniquement des shreds de leader. Les pics de trafic doivent s'aligner avec vos slots de leader. Lorsque vous n'avez pas de slot de leader, il ne doit y avoir aucun trafic. Si vous retransmettez, vous verrez un flux de trafic constant au lieu de pics alignés sur les slots. +Le graphique montre si vous envoyez uniquement des shreds de leader. Les pics de trafic devraient correspondre aux moments où vous avez un slot de leader. Lorsque vous n'avez pas de slot de leader, il ne devrait y avoir aucun trafic. Si vous retransmettez, vous verrez un flux de trafic constant au lieu de pics alignés sur les slots. \ No newline at end of file diff --git a/docs/Validator Multicast Connection.it.md b/docs/Validator Multicast Connection.it.md index 883925c..f3b4910 100644 --- a/docs/Validator Multicast Connection.it.md +++ b/docs/Validator Multicast Connection.it.md @@ -1,14 +1,16 @@ -# Connessione Multicast Validatore -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Configura un validatore connesso per pubblicare gli shred leader nel feed multicast edge di DoubleZero. +--- +# Connessione Multicast del Validatore !!! warning "Connettendomi a DoubleZero accetto i [Termini di Servizio di DoubleZero](https://doublezero.xyz/terms-protocol)" !!! note inline end "Società di trading e aziende" - Se gestisci una società di trading o un'azienda che desidera iscriversi al feed, registra il tuo interesse per ricevere più informazioni [qui](https://doublezero.xyz/edge-form). + Se gestisci una società di trading o un'azienda interessata a sottoscrivere il feed, registra il tuo interesse per ottenere maggiori informazioni [qui](https://doublezero.xyz/edge-form). -Se non sei ancora connesso a DoubleZero, completa prima la documentazione di [Configurazione](setup.md) e di connessione validatore [Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md). +Se non sei ancora connesso a DoubleZero, completa prima la documentazione relativa al [Setup]() e alla connessione del validatore a [Mainnet-Beta](). -Se sei un validatore già connesso a DoubleZero, puoi continuare questa guida. +Se sei un validatore già connesso a DoubleZero, puoi proseguire con questa guida. ## 1. Configurazione del Client @@ -16,9 +18,9 @@ Se sei un validatore già connesso a DoubleZero, puoi continuare questa guida. 1. Nel tuo script di avvio del validatore, aggiungi: `--shred-receiver-address 233.84.178.1:7733` - Puoi inviare a Jito e al gruppo `edge-solana-shreds` contemporaneamente. + Puoi inviare contemporaneamente a Jito e al gruppo `edge-solana-shreds`. - Esempio: + esempio: ```json #!/bin/bash @@ -45,47 +47,66 @@ Se sei un validatore già connesso a DoubleZero, puoi continuare questa guida. 2. Riavvia il tuo validatore. 3. Connettiti al gruppo multicast DoubleZero `edge-solana-shreds` come publisher: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` -## 2. Confermare la pubblicazione degli shred leader +## 2. Verifica che stai pubblicando gli shred leader -Una volta connesso, puoi verificare [questa dashboard](https://data.doublezero.xyz/dz/publisher-check) per confermare che stai pubblicando shred. Non vedrai la conferma finché non avrai pubblicato shred leader per almeno uno slot. +Una volta connesso, puoi controllare [questa dashboard](https://data.doublezero.xyz/dz/publisher-check) per confermare che stai pubblicando gli shred. Non vedrai la conferma finché non avrai pubblicato gli shred leader per almeno uno slot. + +## Endpoint Multicast (IP vs Porta) + +Per il traffico shred, l'**indirizzo IP** seleziona il feed multicast e la **porta** seleziona il servizio UDP. +Tutti i feed seguenti utilizzano la porta UDP `7733`. + +Puoi scoprire gli IP dei gruppi attuali con: + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds` (leader): `233.84.178.1:7733` +- `edge-solana-retrans-eu`: `233.84.178.12:7733` +- `edge-solana-retrans-apac`: `233.84.178.13:7733` +- `edge-solana-retrans-amer`: `233.84.178.14:7733` + +Per i riferimenti API e gli endpoint dati leggibili dalle macchine, consulta [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs). ## 3. Ricompense per i Validatori -Per ogni epoca in cui i validatori pubblicano shred leader, verranno ricompensati proporzionalmente per il loro contributo in base alle sottoscrizioni. I dettagli di questo sistema saranno annunciati e approfonditi in una data successiva. +Per ogni epoca in cui i validatori pubblicano shred leader, saranno ricompensati proporzionalmente per il loro contributo in base alle sottoscrizioni. I dettagli specifici di questo sistema saranno annunciati e descritti in una data successiva. ## Risoluzione dei Problemi -### Mancata pubblicazione degli shred leader: +### Mancata Pubblicazione degli Shred Leader: La causa più comune della mancata trasmissione degli shred è la versione del client: -Devi eseguire Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer o Harmonic 3.1.11+. Le altre versioni del client non funzioneranno. +Devi utilizzare Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer o Harmonic 3.1.11+. Altre versioni del client non funzioneranno. ### Ritrasmissione: -1. Una causa comune di ritrasmissione degli shred è una semplice configurazione. Potresti avere il flag abilitato per inviare shred di ritrasmissione nel tuo script di avvio; dovrai disabilitarlo. +1. Una causa comune della ritrasmissione degli shred è una semplice configurazione errata. Potresti avere il flag abilitato per inviare gli shred di ritrasmissione nel tuo script di avvio; dovrai disabilitarlo. Il flag da rimuovere in Jito-Agave è: `--shred-retransmit-receiver-address`. -1. Controlla la [dashboard dei publisher](https://data.doublezero.xyz/dz/publisher-check) e verifica se hai shred ritrasmessi. Nella tabella, guarda la colonna **No Retransmit Shreds**—una X rossa significa che stai ritrasmettendo. +1. Controlla la [dashboard del publisher](https://data.doublezero.xyz/dz/publisher-check) e verifica se hai shred ritrasmessi. Nella tabella, guarda la colonna **No Retransmit Shreds** — una X rossa indica che stai ritrasmettendo. + + !!! note "vista per epoca" + Nota che ci sono diverse finestre temporali per visualizzare la dashboard del publisher. Se vedi ritrasmissione nella **vista a 2 epoche**, ma hai apportato una modifica recente, prova a passare alla vista **slot recente**. - !!! note "Vista per epoca" - Tieni presente che ci sono diverse finestre temporali per visualizzare la dashboard dei publisher. Se vedi la ritrasmissione nella **vista 2 epoche**, ma hai apportato una modifica recente, prova a passare alla vista **slot recente**. - ![Dashboard di verifica publisher](images/publisher-check-dashboard.png) + ![Dashboard di verifica del publisher](images/publisher-check-dashboard.png) -2. Trova l'IP del tuo client e cerca il tuo utente in [DoubleZero Data](https://data.doublezero.xyz/dz/users). +2. Trova il tuo IP client e cerca il tuo utente in [DoubleZero Data](https://data.doublezero.xyz/dz/users). ![Utenti DoubleZero Data](images/doublezero-data-users.png) 3. Clicca su **Multicast** per aprire la tua vista multicast. - Lo screenshot seguente mostra: **Ritrasmissione** (indesiderata) traffico in uscita costante senza pattern di slot leader. + Lo screenshot qui sotto mostra: **Ritrasmissione** (indesiderabile) traffico in uscita costante senza un pattern di slot leader. ![Vista multicast utente - esempio di ritrasmissione](images/user-multicast-view-retransmit.png) - Lo screenshot seguente mostra: **Sano** (pubblicazione solo di shred leader) traffico in uscita a picchi, noto come pattern a dente di sega, che si allinea con i tuoi slot leader. + Lo screenshot qui sotto mostra: **Sano** (pubblicazione dei soli shred leader) traffico in uscita a picchi, noto come pattern a dente di sega, che si allinea con i tuoi slot leader. ![Vista multicast utente - esempio di publisher sano](images/user-multicast-view-healthy.png) -Il grafico mostra se stai inviando solo shred leader. I picchi di traffico dovrebbero allinearsi con quando hai uno slot leader. Quando non hai uno slot leader non ci dovrebbe essere traffico. Se stai ritrasmettendo, vedrai un flusso costante di traffico invece di picchi allineati agli slot. +Il grafico mostra se stai inviando solo gli shred leader. I picchi di traffico dovrebbero allinearsi con i momenti in cui hai uno slot leader. Quando non hai uno slot leader non dovrebbe esserci traffico. Se stai ritrasmettendo, vedrai un flusso costante di traffico invece di picchi allineati agli slot. \ No newline at end of file diff --git a/docs/Validator Multicast Connection.ja.md b/docs/Validator Multicast Connection.ja.md index 964f444..295b0c2 100644 --- a/docs/Validator Multicast Connection.ja.md +++ b/docs/Validator Multicast Connection.ja.md @@ -1,24 +1,26 @@ -# バリデーターマルチキャスト接続 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 接続済みのバリデーターを設定して、リーダーシュレッドをDoubleZeroマルチキャストエッジフィードにパブリッシュします。 +--- -!!! warning "DoubleZeroに接続することで、[DoubleZero利用規約](https://doublezero.xyz/terms-protocol)に同意します" +# バリデーターマルチキャスト接続 +!!! warning "DoubleZeroに接続することで、[DoubleZero利用規約](https://doublezero.xyz/terms-protocol)に同意したものとみなされます" -!!! note inline end "トレーディングファームおよびビジネス" - トレーディングファームやフィードへの購読を検討している企業の方は、詳細については[こちら](https://doublezero.xyz/edge-form)からご登録ください。 +!!! note inline end "トレーディング企業およびビジネス" + トレーディング企業やビジネスを運営しており、フィードの購読をご検討の方は、[こちら](https://doublezero.xyz/edge-form)から詳細情報のリクエストをご登録ください。 -DoubleZeroにまだ接続していない場合は、[セットアップ](setup.md)と[Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md)バリデーター接続ドキュメントを完了してください。 +まだDoubleZeroに接続していない場合は、[セットアップ]()および[Mainnet-Beta]()バリデーター接続のドキュメントを完了してください。 -DoubleZeroにすでに接続しているバリデーターの方は、このガイドを続けてください。 +すでにDoubleZeroに接続済みのバリデーターの方は、このガイドを続行してください。 ## 1. クライアント設定 -### Jito-Agave(v3.1.9+)および Harmonic(3.1.11+) +### Jito-Agave (v3.1.9+) および Harmonic (3.1.11+) -1. バリデーターの起動スクリプトに以下を追加します:`--shred-receiver-address 233.84.178.1:7733` +1. バリデーターの起動スクリプトに以下を追加します: `--shred-receiver-address 233.84.178.1:7733` - JitoとDoubleZeroの`edge-solana-shreds`グループに同時に送信できます。 + Jitoと`edge-solana-shreds`グループに同時に送信することが可能です。 - 例: + 例: ```json #!/bin/bash @@ -31,11 +33,11 @@ DoubleZeroにすでに接続しているバリデーターの方は、このガ ``` 2. バリデーターを再起動します。 -3. パブリッシャーとしてDoubleZeroマルチキャストグループ`edge-solana-shreds`に接続します:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. DoubleZeroマルチキャストグループ`edge-solana-shreds`にパブリッシャーとして接続します: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer -1. `config.toml`に以下を追加します: +1. `config.toml`に以下を追加します: ```toml [tiles.shred] @@ -43,49 +45,68 @@ DoubleZeroにすでに接続しているバリデーターの方は、このガ ``` 2. バリデーターを再起動します。 -3. パブリッシャーとしてDoubleZeroマルチキャストグループ`edge-solana-shreds`に接続します:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. DoubleZeroマルチキャストグループ`edge-solana-shreds`にパブリッシャーとして接続します: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` + +## 2. リーダーシュレッドのパブリッシュを確認する + +接続が完了したら、[このダッシュボード](https://data.doublezero.xyz/dz/publisher-check)でシュレッドがパブリッシュされていることを確認できます。少なくとも1スロット分のリーダーシュレッドをパブリッシュするまで、確認は表示されません。 + +## マルチキャストエンドポイント(IP対ポート) -## 2. リーダーシュレッドの発行確認 +シュレッドトラフィックでは、**IPアドレス**がマルチキャストフィードを選択し、**ポート**がUDPサービスを選択します。 +以下のすべてのフィードはUDPポート`7733`を使用します。 -接続後、[このダッシュボード](https://data.doublezero.xyz/dz/publisher-check)でシュレッドを発行していることを確認できます。少なくとも1スロット分のリーダーシュレッドを発行した後でないと確認は表示されません。 +現在のグループIPは以下のコマンドで確認できます: + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds`(リーダー): `233.84.178.1:7733` +- `edge-solana-retrans-eu`: `233.84.178.12:7733` +- `edge-solana-retrans-apac`: `233.84.178.13:7733` +- `edge-solana-retrans-amer`: `233.84.178.14:7733` + +APIリファレンスおよび機械可読データエンドポイントについては、[https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs)を参照してください。 ## 3. バリデーター報酬 -バリデーターがリーダーシュレッドを発行した各エポックにおいて、サブスクリプションに基づいた貢献度に応じて比例的に報酬が付与されます。このシステムの詳細は後日発表・詳述される予定です。 +バリデーターがリーダーシュレッドをパブリッシュした各エポックにおいて、サブスクリプションに基づいた貢献度に応じて比例配分で報酬が付与されます。このシステムの詳細については、後日発表および公開される予定です。 ## トラブルシューティング -### リーダーシュレッドが発行されない場合: +### リーダーシュレッドがパブリッシュされない場合: -シュレッドが送信されない最も一般的な原因はクライアントのバージョンです: +シュレッドが送信されない最も一般的な原因は、クライアントのバージョンです: -Jito-Agave 3.1.9+、JitoBam 3.1.9+、Frankendancer、またはHarmonic 3.1.11+を実行している必要があります。他のクライアントバージョンは動作しません。 +Jito-Agave 3.1.9+、JitoBam 3.1.9+、Frankendancer、またはHarmonic 3.1.11+を実行している必要があります。その他のクライアントバージョンでは動作しません。 -### 再送信している場合: +### 再送信(リトランスミット)している場合: -1. シュレッドの再送信の一般的な原因は設定の問題です。起動スクリプトで再送信シュレッドを送信するフラグが有効になっている場合は、無効にする必要があります。 +1. シュレッド再送信の一般的な原因は、シンプルな設定ミスです。起動スクリプトで再送信シュレッドを送信するフラグが有効になっている可能性があります。これを無効にする必要があります。 - Jito-Agaveで削除するフラグは:`--shred-retransmit-receiver-address`です。 + Jito-Agaveで削除すべきフラグは: `--shred-retransmit-receiver-address`です。 -1. [パブリッシャーダッシュボード](https://data.doublezero.xyz/dz/publisher-check)を確認し、再送信シュレッドがあるかどうかを確認します。表の**No Retransmit Shreds**列を確認してください——赤いXは再送信していることを意味します。 +1. [パブリッシャーダッシュボード](https://data.doublezero.xyz/dz/publisher-check)を確認し、再送信されたシュレッドがないか確認してください。テーブルの**No Retransmit Shreds**列を確認し、赤いXが表示されている場合は再送信が行われています。 !!! note "エポックビュー" - パブリッシャーダッシュボードには異なる時間ウィンドウがあります。**2エポックビュー**で再送信が見られるが最近変更を加えた場合は、**最近のスロット**ビューに切り替えてみてください。 + パブリッシャーダッシュボードには異なる時間ウィンドウがあります。**2エポックビュー**で再送信が表示されていても、最近変更を行った場合は、**最近のスロット**ビューに切り替えてみてください。 + ![パブリッシャーチェックダッシュボード](images/publisher-check-dashboard.png) -2. クライアントIPを見つけ、[DoubleZeroデータ](https://data.doublezero.xyz/dz/users)でユーザーを検索します。 +2. クライアントIPを確認し、[DoubleZero Data](https://data.doublezero.xyz/dz/users)でユーザーを検索してください。 - ![DoubleZeroデータユーザー](images/doublezero-data-users.png) + ![DoubleZero Dataユーザー](images/doublezero-data-users.png) -3. **マルチキャスト**をクリックしてマルチキャストビューを開きます。 +3. **Multicast**をクリックしてマルチキャストビューを開きます。 - 以下のスクリーンショットは:**再送信**(望ましくない)リーダースロットパターンのない安定した送信トラフィックを示しています。 + 以下のスクリーンショットは: **再送信中**(望ましくない状態)— リーダースロットのパターンがなく、一定のアウトバウンドトラフィックが表示されています。 ![ユーザーマルチキャストビュー - 再送信の例](images/user-multicast-view-retransmit.png) - 以下のスクリーンショットは:**正常**(リーダーシュレッドのみを発行)スパイク状の送信トラフィック(鋸歯状波パターン)を示しており、リーダースロットと一致しています。 + 以下のスクリーンショットは: **正常な状態**(リーダーシュレッドのみをパブリッシュ)— リーダースロットに合わせてスパイク状(ノコギリ波パターン)のアウトバウンドトラフィックが表示されています。 ![ユーザーマルチキャストビュー - 正常なパブリッシャーの例](images/user-multicast-view-healthy.png) -チャートはリーダーシュレッドのみを送信しているかどうかを示します。トラフィックのスパイクはリーダースロットがある時と一致するはずです。リーダースロットがない場合はトラフィックがないはずです。再送信している場合は、スロットに合わせたスパイクではなく、安定したトラフィックの流れが見えます。 +このチャートは、リーダーシュレッドのみを送信しているかどうかを示しています。トラフィックスパイクは、リーダースロットを持つタイミングと一致するはずです。リーダースロットがない場合、トラフィックは発生しないはずです。再送信している場合は、スロットに合わせたスパイクではなく、一定のトラフィックフローが表示されます。 \ No newline at end of file diff --git a/docs/Validator Multicast Connection.ko.md b/docs/Validator Multicast Connection.ko.md index 47c7d3d..9918fbe 100644 --- a/docs/Validator Multicast Connection.ko.md +++ b/docs/Validator Multicast Connection.ko.md @@ -1,12 +1,14 @@ -# 검증자 멀티캐스트 연결 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 연결된 검증자를 구성하여 리더 shred를 DoubleZero 멀티캐스트 엣지 피드에 게시합니다. +--- -!!! warning "DoubleZero에 연결함으로써 [DoubleZero 이용약관](https://doublezero.xyz/terms-protocol)에 동의합니다" +# 검증자 멀티캐스트 연결 +!!! warning "DoubleZero에 연결함으로써 [DoubleZero 서비스 약관](https://doublezero.xyz/terms-protocol)에 동의합니다" -!!! note inline end "거래 회사 및 기업" - 피드를 구독하려는 거래 회사나 기업을 운영하시는 경우, 더 많은 정보를 얻으려면 [여기](https://doublezero.xyz/edge-form)에서 관심을 등록하세요. +!!! note inline end "트레이딩 회사 및 기업" + 트레이딩 회사 또는 기업을 운영하고 있으며 피드 구독을 원하시는 경우 [여기](https://doublezero.xyz/edge-form)에서 관심을 등록하여 자세한 정보를 받으세요. -DoubleZero에 아직 연결되지 않은 경우 [설정](setup.md) 및 [Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md) 검증자 연결 문서를 먼저 완료하세요. +아직 DoubleZero에 연결하지 않았다면 [설정]() 및 [Mainnet-Beta]() 검증자 연결 문서를 먼저 완료해 주세요. 이미 DoubleZero에 연결된 검증자라면 이 가이드를 계속 진행할 수 있습니다. @@ -14,11 +16,11 @@ DoubleZero에 아직 연결되지 않은 경우 [설정](setup.md) 및 [Mainnet- ### Jito-Agave (v3.1.9+) 및 Harmonic (3.1.11+) -1. 검증자 시작 스크립트에 다음을 추가합니다:`--shred-receiver-address 233.84.178.1:7733` +1. 검증자 시작 스크립트에 다음을 추가합니다: `--shred-receiver-address 233.84.178.1:7733` Jito와 `edge-solana-shreds` 그룹에 동시에 전송할 수 있습니다. - 예시: + 예시: ```json #!/bin/bash @@ -31,11 +33,11 @@ DoubleZero에 아직 연결되지 않은 경우 [설정](setup.md) 및 [Mainnet- ``` 2. 검증자를 재시작합니다. -3. 게시자로서 DoubleZero 멀티캐스트 그룹 `edge-solana-shreds`에 연결합니다:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. DoubleZero 멀티캐스트 그룹 `edge-solana-shreds`에 게시자로 연결합니다: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer -1. `config.toml`에 다음을 추가합니다: +1. `config.toml`에 다음을 추가합니다: ```toml [tiles.shred] @@ -43,49 +45,68 @@ DoubleZero에 아직 연결되지 않은 경우 [설정](setup.md) 및 [Mainnet- ``` 2. 검증자를 재시작합니다. -3. 게시자로서 DoubleZero 멀티캐스트 그룹 `edge-solana-shreds`에 연결합니다:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. DoubleZero 멀티캐스트 그룹 `edge-solana-shreds`에 게시자로 연결합니다: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` + +## 2. 리더 shred 게시 확인 + +연결이 완료되면 [이 대시보드](https://data.doublezero.xyz/dz/publisher-check)에서 shred를 게시하고 있는지 확인할 수 있습니다. 최소 하나의 슬롯에 대해 리더 shred를 게시한 후에야 확인이 가능합니다. + +## 멀티캐스트 엔드포인트 (IP vs 포트) -## 2. 리더 슈레드 게시 확인 +shred 트래픽의 경우, **IP 주소**는 멀티캐스트 피드를 선택하고 **포트**는 UDP 서비스를 선택합니다. +아래의 모든 피드는 UDP 포트 `7733`을 사용합니다. -연결 후 [이 대시보드](https://data.doublezero.xyz/dz/publisher-check)에서 슈레드를 게시하고 있는지 확인할 수 있습니다. 최소 한 슬롯의 리더 슈레드를 게시한 후에야 확인이 표시됩니다. +다음 명령으로 현재 그룹 IP를 확인할 수 있습니다: + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds` (리더): `233.84.178.1:7733` +- `edge-solana-retrans-eu`: `233.84.178.12:7733` +- `edge-solana-retrans-apac`: `233.84.178.13:7733` +- `edge-solana-retrans-amer`: `233.84.178.14:7733` + +API 참조 및 기계 판독 가능한 데이터 엔드포인트는 [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs)를 참조하세요. ## 3. 검증자 보상 -검증자가 리더 슈레드를 게시하는 각 에포크에 대해, 구독에 따라 기여도에 비례하여 보상이 지급됩니다. 이 시스템의 세부 사항은 추후 발표 및 상세히 설명될 예정입니다. +검증자가 리더 shred를 게시한 각 에포크에 대해, 구독에 기반하여 기여도에 비례하는 보상을 받게 됩니다. 이 시스템의 세부 사항은 추후 공지 및 상세히 안내될 예정입니다. ## 문제 해결 -### 리더 슈레드가 게시되지 않는 경우: +### 리더 shred가 게시되지 않는 경우: -슈레드가 전송되지 않는 가장 일반적인 원인은 클라이언트 버전입니다: +shred가 전송되지 않는 가장 흔한 원인은 클라이언트 버전입니다: -Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer 또는 Harmonic 3.1.11+를 실행해야 합니다. 다른 클라이언트 버전은 작동하지 않습니다. +Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer 또는 Harmonic 3.1.11+을 실행해야 합니다. 다른 클라이언트 버전은 작동하지 않습니다. -### 재전송 중인 경우: +### 재전송 문제: -1. 슈레드 재전송의 일반적인 원인은 간단한 구성 문제입니다. 시작 스크립트에서 재전송 슈레드를 보내는 플래그가 활성화되어 있을 수 있습니다. 비활성화해야 합니다. +1. shred 재전송의 일반적인 원인은 단순한 구성 문제입니다. 시작 스크립트에서 재전송 shred를 보내는 플래그가 활성화되어 있을 수 있으며, 이를 비활성화해야 합니다. - Jito-Agave에서 제거해야 할 플래그는:`--shred-retransmit-receiver-address`입니다. + Jito-Agave에서 제거해야 할 플래그: `--shred-retransmit-receiver-address`. -1. [게시자 대시보드](https://data.doublezero.xyz/dz/publisher-check)를 확인하여 재전송된 슈레드가 있는지 확인하세요. 테이블에서 **No Retransmit Shreds** 열을 확인하세요——빨간 X는 재전송 중임을 의미합니다. +1. [게시자 대시보드](https://data.doublezero.xyz/dz/publisher-check)를 확인하여 재전송된 shred가 있는지 확인합니다. 테이블에서 **No Retransmit Shreds** 열을 확인하세요—빨간색 X는 재전송 중임을 의미합니다. !!! note "에포크 뷰" - 게시자 대시보드에는 다른 시간 창이 있습니다. **2 에포크 뷰**에서 재전송이 보이지만 최근에 변경했다면 **최근 슬롯** 뷰로 전환해 보세요. + 게시자 대시보드를 확인할 수 있는 다양한 시간 창이 있습니다. **2 에포크 뷰**에서 재전송이 보이지만 최근 변경을 한 경우, **최근 슬롯** 뷰로 전환해 보세요. + ![게시자 확인 대시보드](images/publisher-check-dashboard.png) -2. 클라이언트 IP를 찾아 [DoubleZero 데이터](https://data.doublezero.xyz/dz/users)에서 사용자를 조회합니다. +2. 클라이언트 IP를 찾고 [DoubleZero Data](https://data.doublezero.xyz/dz/users)에서 사용자를 조회합니다. - ![DoubleZero 데이터 사용자](images/doublezero-data-users.png) + ![DoubleZero Data 사용자](images/doublezero-data-users.png) -3. **멀티캐스트**를 클릭하여 멀티캐스트 뷰를 엽니다. +3. **Multicast**를 클릭하여 멀티캐스트 뷰를 엽니다. - 아래 스크린샷은:**재전송** (바람직하지 않음) 리더 슬롯 패턴 없는 안정적인 아웃바운드 트래픽을 보여줍니다. + 아래 스크린샷은 다음을 보여줍니다: **재전송 중** (바람직하지 않음) 리더 슬롯 패턴 없이 지속적인 아웃바운드 트래픽. ![사용자 멀티캐스트 뷰 - 재전송 예시](images/user-multicast-view-retransmit.png) - 아래 스크린샷은:**정상** (리더 슈레드만 게시) 스파이크 형태의 아웃바운드 트래픽(톱니파 패턴)을 보여주며, 리더 슬롯과 일치합니다. + 아래 스크린샷은 다음을 보여줍니다: **정상** (리더 shred만 게시) 스파이크 형태의 아웃바운드 트래픽, 톱니파(sawtooth) 패턴으로 알려져 있으며, 리더 슬롯과 일치합니다. ![사용자 멀티캐스트 뷰 - 정상 게시자 예시](images/user-multicast-view-healthy.png) -차트는 리더 슈레드만 보내고 있는지 보여줍니다. 트래픽 스파이크는 리더 슬롯이 있을 때와 일치해야 합니다. 리더 슬롯이 없을 때는 트래픽이 없어야 합니다. 재전송 중이라면 슬롯에 맞춘 스파이크 대신 안정적인 트래픽 흐름이 보일 것입니다. +차트는 리더 shred만 전송하고 있는지 보여줍니다. 트래픽 스파이크는 리더 슬롯이 있는 시점과 일치해야 합니다. 리더 슬롯이 없을 때는 트래픽이 없어야 합니다. 재전송 중인 경우, 슬롯에 맞춘 스파이크 대신 지속적인 트래픽 흐름이 보입니다. \ No newline at end of file diff --git a/docs/Validator Multicast Connection.md b/docs/Validator Multicast Connection.md index b18288e..05179d6 100644 --- a/docs/Validator Multicast Connection.md +++ b/docs/Validator Multicast Connection.md @@ -8,7 +8,7 @@ description: Configure a connected validator to publish leader shreds to the Dou !!! note inline end "Trading firms and businesses" If you operate a trading firm or business looking to subscribe to the feed please register interest to get more information [here](https://doublezero.xyz/edge-form). -If you are not already connected to DoubleZero please complete [Setup](https://docs.malbeclabs.com/setup/), and [Mainnet-Beta](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) validator connection documentation. +If you are not already connected to DoubleZero please complete [Setup](), and [Mainnet-Beta]() validator connection documentation. If you are a validator who is already connected to DoubleZero you may continue this guide. diff --git a/docs/Validator Multicast Connection.pt.md b/docs/Validator Multicast Connection.pt.md index 67be325..1c0e742 100644 --- a/docs/Validator Multicast Connection.pt.md +++ b/docs/Validator Multicast Connection.pt.md @@ -1,14 +1,16 @@ -# Conexão Multicast para Validadores -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Configure um validador conectado para publicar leader shreds no feed multicast DoubleZero edge. +--- -!!! warning "Ao me conectar ao DoubleZero, concordo com os [Termos de Serviço do DoubleZero](https://doublezero.xyz/terms-protocol)" +# Conexão Multicast do Validador +!!! warning "Ao conectar-se ao DoubleZero, concordo com os [Termos de Serviço do DoubleZero](https://doublezero.xyz/terms-protocol)" !!! note inline end "Empresas de trading e negócios" - Se você opera uma empresa de trading ou negócio que deseja se inscrever no feed, registre seu interesse para obter mais informações [aqui](https://doublezero.xyz/edge-form). + Se você opera uma empresa de trading ou negócio que deseja assinar o feed, por favor registre seu interesse para obter mais informações [aqui](https://doublezero.xyz/edge-form). -Se você ainda não está conectado ao DoubleZero, complete a documentação de [Configuração](setup.md) e de conexão de validador [Mainnet-Beta](DZ%20Mainnet-beta%20Connection.md). +Se você ainda não está conectado ao DoubleZero, por favor complete a documentação de [Configuração]() e de conexão do validador à [Mainnet-Beta](). -Se você é um validador já conectado ao DoubleZero, pode continuar este guia. +Se você é um validador que já está conectado ao DoubleZero, pode continuar este guia. ## 1. Configuração do Cliente @@ -18,7 +20,7 @@ Se você é um validador já conectado ao DoubleZero, pode continuar este guia. Você pode enviar para o Jito e para o grupo `edge-solana-shreds` ao mesmo tempo. - Exemplo: + exemplo: ```json #!/bin/bash @@ -31,11 +33,11 @@ Se você é um validador já conectado ao DoubleZero, pode continuar este guia. ``` 2. Reinicie seu validador. -3. Conecte-se ao grupo multicast do DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Conecte-se ao grupo multicast DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer -1. Em `config.toml`, adicione: +1. No `config.toml`, adicione: ```toml [tiles.shred] @@ -43,49 +45,68 @@ Se você é um validador já conectado ao DoubleZero, pode continuar este guia. ``` 2. Reinicie seu validador. -3. Conecte-se ao grupo multicast do DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +3. Conecte-se ao grupo multicast DoubleZero `edge-solana-shreds` como publicador: `doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` -## 2. Confirmar que está publicando shreds de líder +## 2. Confirme que você está publicando leader shreds -Após a conexão, você pode verificar [este painel](https://data.doublezero.xyz/dz/publisher-check) para confirmar que está publicando shreds. Você não verá a confirmação até ter publicado shreds de líder para pelo menos um slot. +Depois de conectado, você pode verificar [este painel](https://data.doublezero.xyz/dz/publisher-check) para confirmar que está publicando shreds. Você não verá confirmação até ter publicado leader shreds para pelo menos um slot. -## 3. Recompensas para Validadores +## Endpoints Multicast (IP vs Porta) -Para cada época em que os validadores publicam shreds de líder, eles serão recompensados proporcionalmente pela sua contribuição com base nas assinaturas. Os detalhes deste sistema serão anunciados e detalhados em uma data posterior. +Para tráfego de shreds, o **endereço IP** seleciona o feed multicast e a **porta** seleciona o serviço UDP. +Todos os feeds abaixo utilizam a porta UDP `7733`. -## Solução de Problemas +Você pode descobrir os IPs de grupo atuais com: -### Não está publicando shreds de líder: +```bash +doublezero multicast group list +``` -A causa mais comum de não transmitir shreds é a versão do cliente: +- `edge-solana-shreds` (leader): `233.84.178.1:7733` +- `edge-solana-retrans-eu`: `233.84.178.12:7733` +- `edge-solana-retrans-apac`: `233.84.178.13:7733` +- `edge-solana-retrans-amer`: `233.84.178.14:7733` + +Para referências de API e endpoints de dados legíveis por máquina, consulte [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs). + +## 3. Recompensas do Validador + +Para cada epoch em que os validadores publicam leader shreds, eles serão recompensados proporcionalmente pela sua contribuição com base nas assinaturas. Os detalhes deste sistema serão anunciados e detalhados em uma data posterior. + +## Resolução de Problemas + +### Não Está Publicando Leader Shreds: + +A causa mais comum para não transmitir shreds é a versão do cliente: Você deve estar executando Jito-Agave 3.1.9+, JitoBam 3.1.9+, Frankendancer ou Harmonic 3.1.11+. Outras versões de cliente não funcionarão. -### Retransmissão: +### Retransmitindo: + +1. Uma causa comum de retransmissão de shreds é uma configuração simples. Você pode ter a flag habilitada para enviar shreds de retransmissão no seu script de inicialização; será necessário desabilitá-la. -1. Uma causa comum de retransmissão de shreds é uma configuração simples. Você pode ter o flag habilitado para enviar shreds de retransmissão no seu script de inicialização; você precisará desabilitá-lo. + A flag a ser removida no Jito-Agave é: `--shred-retransmit-receiver-address`. - O flag a remover no Jito-Agave é: `--shred-retransmit-receiver-address`. +1. Verifique o [painel do publicador](https://data.doublezero.xyz/dz/publisher-check) e veja se você tem algum shred retransmitido. Na tabela, observe a coluna **No Retransmit Shreds** — um X vermelho significa que você está retransmitindo. -1. Verifique o [painel de publicadores](https://data.doublezero.xyz/dz/publisher-check) e veja se há shreds retransmitidos. Na tabela, observe a coluna **No Retransmit Shreds**—um X vermelho significa que você está retransmitindo. + !!! note "visualização por epoch" + Note que existem diferentes janelas de tempo para visualizar o painel do publicador. Se você vir retransmissão na **visualização de 2 epochs**, mas fez uma alteração recente, tente mudar para a visualização de **slot recente**. - !!! note "Visão por época" - Observe que há diferentes janelas de tempo para visualizar o painel de publicadores. Se você vê retransmissão na **visão de 2 épocas**, mas fez uma alteração recente, tente mudar para a visão de **slot recente**. - ![Painel de verificação de publicadores](images/publisher-check-dashboard.png) + ![Painel de verificação do publicador](images/publisher-check-dashboard.png) 2. Encontre o IP do seu cliente e procure seu usuário em [DoubleZero Data](https://data.doublezero.xyz/dz/users). ![Usuários do DoubleZero Data](images/doublezero-data-users.png) -3. Clique em **Multicast** para abrir sua visão multicast. +3. Clique em **Multicast** para abrir sua visualização multicast. - A captura de tela abaixo mostra: **Retransmitindo** (indesejável) tráfego de saída constante sem padrão de slot de líder. + A captura de tela abaixo mostra: **Retransmitindo** (indesejável) tráfego de saída constante sem padrão de leader-slot. - ![Visão multicast do usuário - exemplo de retransmissão](images/user-multicast-view-retransmit.png) + ![Visualização multicast do usuário - exemplo de retransmissão](images/user-multicast-view-retransmit.png) - A captura de tela abaixo mostra: **Saudável** (publicando apenas shreds de líder) tráfego de saída em picos, conhecido como padrão de dente de serra, que se alinha com seus slots de líder. + A captura de tela abaixo mostra: **Saudável** (publicando apenas leader shreds) tráfego de saída em picos, conhecido como padrão dente de serra, que se alinham com seus leader slots. - ![Visão multicast do usuário - exemplo de publicador saudável](images/user-multicast-view-healthy.png) + ![Visualização multicast do usuário - exemplo de publicador saudável](images/user-multicast-view-healthy.png) -O gráfico mostra se você está enviando apenas shreds de líder. Os picos de tráfego devem se alinhar com quando você tem um slot de líder. Quando não há slot de líder, não deve haver tráfego. Se você estiver retransmitindo, verá um fluxo constante de tráfego em vez de picos alinhados com slots. +O gráfico mostra se você está enviando apenas leader shreds. Os picos de tráfego devem se alinhar com quando você tem um leader slot. Quando você não tem um leader slot, não deve haver tráfego. Se você está retransmitindo, verá um fluxo constante de tráfego em vez de picos alinhados aos slots. \ No newline at end of file diff --git a/docs/Validator Multicast Connection.zh.md b/docs/Validator Multicast Connection.zh.md index 4b89283..cc2d203 100644 --- a/docs/Validator Multicast Connection.zh.md +++ b/docs/Validator Multicast Connection.zh.md @@ -1,22 +1,24 @@ -# 验证器多播连接 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 配置已连接的验证者,将领导者 shred 发布到 DoubleZero 组播边缘 feed。 +--- -!!! warning "通过连接到DoubleZero,我同意[DoubleZero服务条款](https://doublezero.xyz/terms-protocol)" +# 验证者组播连接 +!!! warning "连接到 DoubleZero 即表示我同意 [DoubleZero 服务条款](https://doublezero.xyz/terms-protocol)" !!! note inline end "交易公司和企业" - 如果您经营交易公司或企业,希望订阅数据流,请在[此处](https://doublezero.xyz/edge-form)注册以获取更多信息。 + 如果您经营交易公司或企业并希望订阅该 feed,请在[此处](https://doublezero.xyz/edge-form)注册以获取更多信息。 -如果您尚未连接到DoubleZero,请先完成[设置](setup.md)和[主网Beta](DZ%20Mainnet-beta%20Connection.md)验证器连接文档。 +如果您尚未连接到 DoubleZero,请先完成 [Setup]() 和 [Mainnet-Beta]() 验证者连接文档。 -如果您是已连接到DoubleZero的验证器,可以继续阅读本指南。 +如果您是已连接到 DoubleZero 的验证者,可以继续阅读本指南。 ## 1. 客户端配置 -### Jito-Agave(v3.1.9+)和 Harmonic(3.1.11+) +### Jito-Agave (v3.1.9+) 和 Harmonic (3.1.11+) -1. 在您的验证器启动脚本中,添加:`--shred-receiver-address 233.84.178.1:7733` +1. 在您的验证者启动脚本中,添加:`--shred-receiver-address 233.84.178.1:7733` - 您可以同时向Jito和`edge-solana-shreds`组发送数据。 + 您可以同时向 Jito 和 `edge-solana-shreds` 组发送数据。 示例: @@ -30,62 +32,81 @@ --shred-receiver-address 233.84.178.1:7733 ``` -2. 重启您的验证器。 -3. 以发布者身份连接到DoubleZero多播组`edge-solana-shreds`:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +2. 重启您的验证者。 +3. 以发布者身份连接到 DoubleZero 组播组 `edge-solana-shreds`:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` ### Frankendancer -1. 在`config.toml`中,添加: +1. 在 `config.toml` 中,添加: ```toml [tiles.shred] additional_shred_destinations_leader = [ "233.84.178.1:7733", ] ``` -2. 重启您的验证器。 -3. 以发布者身份连接到DoubleZero多播组`edge-solana-shreds`:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` +2. 重启您的验证者。 +3. 以发布者身份连接到 DoubleZero 组播组 `edge-solana-shreds`:`doublezero connect ibrl && doublezero connect multicast --publish edge-solana-shreds` -## 2. 确认您正在发布领导者碎片 +## 2. 确认您正在发布领导者 shred -连接后,您可以查看[此仪表板](https://data.doublezero.xyz/dz/publisher-check)以确认您正在发布碎片。在您至少发布了一个槽位的领导者碎片之后,才能看到确认信息。 +连接成功后,您可以查看[此仪表板](https://data.doublezero.xyz/dz/publisher-check)来确认您正在发布 shred。在您至少发布了一个 slot 的领导者 shred 之后,才会看到确认信息。 -## 3. 验证器奖励 +## 组播端点(IP 与端口) -对于验证器发布领导者碎片的每个纪元,将根据订阅情况按比例奖励其贡献。该系统的具体细节将于稍后公布并详细说明。 +对于 shred 流量,**IP 地址**用于选择组播 feed,**端口**用于选择 UDP 服务。 +以下所有 feed 均使用 UDP 端口 `7733`。 + +您可以使用以下命令查找当前的组 IP: + +```bash +doublezero multicast group list +``` + +- `edge-solana-shreds`(领导者):`233.84.178.1:7733` +- `edge-solana-retrans-eu`:`233.84.178.12:7733` +- `edge-solana-retrans-apac`:`233.84.178.13:7733` +- `edge-solana-retrans-amer`:`233.84.178.14:7733` + +有关 API 参考和机器可读数据端点,请参阅 [https://data.doublezero.xyz/api/v1/docs](https://data.doublezero.xyz/api/v1/docs)。 + +## 3. 验证者奖励 + +对于验证者发布领导者 shred 的每个 epoch,他们将根据订阅情况按比例获得贡献奖励。该系统的具体细节将在稍后公布并详细说明。 ## 故障排除 -### 未发布领导者碎片: +### 未发布领导者 shred: -未传输碎片最常见的原因是客户端版本问题: +无法传输 shred 的最常见原因是客户端版本问题: 您必须运行 Jito-Agave 3.1.9+、JitoBam 3.1.9+、Frankendancer 或 Harmonic 3.1.11+。其他客户端版本将无法工作。 -### 重传: +### 重传问题: + +1. shred 重传的常见原因是配置问题。您的启动脚本中可能启用了发送重传 shred 的标志;您需要将其禁用。 -1. 碎片重传的常见原因是简单的配置问题。您的启动脚本中可能启用了发送重传碎片的标志;您需要禁用它。 + 在 Jito-Agave 中需要移除的标志是:`--shred-retransmit-receiver-address`。 - 在Jito-Agave中需要删除的标志是:`--shred-retransmit-receiver-address`。 +1. 查看[发布者仪表板](https://data.doublezero.xyz/dz/publisher-check),检查您是否有重传的 shred。在表格中,查看 **No Retransmit Shreds** 列——红色 X 表示您正在重传。 -1. 查看[发布者仪表板](https://data.doublezero.xyz/dz/publisher-check),检查是否有重传碎片。在表格中,查看**No Retransmit Shreds**列——红色X表示您正在重传。 + !!! note "epoch 视图" + 请注意,发布者仪表板有不同的时间窗口可供查看。如果您在 **2 epoch 视图**中看到重传,但最近刚进行了更改,请尝试切换到**近期 slot** 视图。 - !!! note "纪元视图" - 注意发布者仪表板有不同的时间窗口可供查看。如果您在**2纪元视图**中看到重传,但最近做了更改,请尝试切换到**近期槽位**视图。 ![发布者检查仪表板](images/publisher-check-dashboard.png) -2. 找到您的客户端IP,并在[DoubleZero数据](https://data.doublezero.xyz/dz/users)中查找您的用户。 +2. 找到您的客户端 IP,并在 [DoubleZero Data](https://data.doublezero.xyz/dz/users) 中查找您的用户。 - ![DoubleZero数据用户](images/doublezero-data-users.png) + ![DoubleZero Data 用户](images/doublezero-data-users.png) -3. 点击**多播**以打开您的多播视图。 +3. 点击 **Multicast** 打开您的组播视图。 - 下图显示:**重传**(不理想)稳定的出站流量,没有领导者槽位模式。 + 下方截图显示:**重传中**(不理想的状态)稳定的出站流量,没有领导者 slot 模式。 - ![用户多播视图 - 重传示例](images/user-multicast-view-retransmit.png) + ![用户组播视图 - 重传示例](images/user-multicast-view-retransmit.png) - 下图显示:**健康**(仅发布领导者碎片)出站流量呈尖峰状,称为锯齿波模式,与您的领导者槽位对齐。 + 下方截图显示:**健康**(仅发布领导者 shred)的出站流量呈峰值状,称为锯齿模式,与您的领导者 slot 对齐。 - ![用户多播视图 - 健康发布者示例](images/user-multicast-view-healthy.png) + ![用户组播视图 - 健康发布者示例](images/user-multicast-view-healthy.png) -图表显示您是否仅发送领导者碎片。流量峰值应与您拥有领导者槽位时对齐。当您没有领导者槽位时,应该没有流量。如果您正在重传,您将看到稳定的流量流,而不是与槽位对齐的峰值。 +该图表显示您是否仅发送领导者 shred。流量峰值应与您拥有领导者 slot 的时间一致。当您没有领导者 slot 时,不应有任何流量。如果您正在重传,您将看到稳定的流量而非与 slot 对齐的峰值。 \ No newline at end of file diff --git a/docs/robots.txt b/docs/robots.txt index b6be479..b3a8552 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -30,4 +30,4 @@ Allow: / User-agent: Google-Extended Allow: / -Sitemap: https://docs.malbeclabs.com/sitemap.xml +Sitemap: https://docs.doublezero.xyz/sitemap.xml diff --git a/docs/tenant.es.md b/docs/tenant.es.md index a954ae1..b132655 100644 --- a/docs/tenant.es.md +++ b/docs/tenant.es.md @@ -1,27 +1,29 @@ -# Inquilinos -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Elija el inquilino y la ruta de conexión de DoubleZero que se ajuste a su caso de uso — validadores de Solana, RPCs, Shelby o un nuevo protocolo. +--- +# Inquilinos -Los inquilinos en DoubleZero son grupos de usuarios con perfiles similares y requisitos de conexión, que típicamente representan diferentes ecosistemas blockchain, capas de transferencia de datos u otros sistemas distribuidos. +Los inquilinos en DoubleZero son grupos de usuarios con perfiles y requisitos de conexión similares, que normalmente representan diferentes ecosistemas de blockchain, capas de transferencia de datos o sistemas distribuidos. -Examine la tabla a continuación y elija el caso de uso que mejor describa cómo planea interactuar con la red DoubleZero. +Examine la tabla a continuación y elija el caso de uso que mejor describa cómo planea interactuar con la Red DoubleZero. Solana -| Caso de Uso | Siguiente Paso | +| Caso de uso | Siguiente paso | |-------------|---------| -| Validador Solana Mainnet, incluyendo respaldos | [Conexión DoubleZero Mainnet-Beta](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Validador Solana Testnet, incluyendo respaldos | [Conexión DoubleZero Testnet](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | +| Validador de Solana Mainnet, incluyendo respaldos | [Conexión DoubleZero Mainnet-Beta]() | +| Validador de Solana Testnet, incluyendo respaldos | [Conexión DoubleZero Testnet]() | | No validadores - RPCs y otros. | [Conexión DoubleZero Mainnet-Beta y Testnet](Permissioned%20Connection.md) | Shelby -| Caso de Uso | Siguiente Paso | +| Caso de uso | Siguiente paso | |-------------|---------| -| RPCs y Nodos de Almacenamiento Shelby Testnet | [Conexión DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | +| RPCs y nodos de almacenamiento de Shelby Testnet | [Conexión DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | -Nuevo Inquilino +Nuevo inquilino -| Caso de Uso | Siguiente Paso | +| Caso de uso | Siguiente paso | -------------|---------| -| Tienes un nuevo protocolo, aplicación u otro caso de uso para aprovechar DoubleZero | [Agregar un Nuevo Inquilino](New%20Tenant.md) | +| Tiene un nuevo protocolo, aplicación u otro caso de uso para aprovechar DoubleZero | [Agregar un nuevo inquilino](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.fr.md b/docs/tenant.fr.md index 13e5e7f..f8e4454 100644 --- a/docs/tenant.fr.md +++ b/docs/tenant.fr.md @@ -1,27 +1,29 @@ -# Tenants -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Choisissez le tenant et le chemin de connexion DoubleZero qui correspond à votre cas d'utilisation — validateurs Solana, RPCs, Shelby ou un nouveau protocole. +--- +# Tenants -Les tenants sur DoubleZero sont des groupes d'utilisateurs aux profils et aux exigences de connexion similaires, représentant généralement différents écosystèmes blockchain, couches de transfert de données ou systèmes distribués. +Les tenants sur DoubleZero sont des groupes d'utilisateurs ayant des profils et des exigences de connexion similaires, représentant généralement différents écosystèmes blockchain, couches de transfert de données ou systèmes distribués. -Examinez le tableau ci-dessous et choisissez le cas d'utilisation qui décrit le mieux la façon dont vous envisagez d'interagir avec le réseau DoubleZero. +Consultez le tableau ci-dessous et choisissez le cas d'utilisation qui décrit le mieux la façon dont vous prévoyez d'interagir avec le réseau DoubleZero. Solana -| Cas d'Utilisation | Prochaine Étape | +| Cas d'utilisation | Étape suivante | |-------------|---------| -| Validateur Solana Mainnet, y compris les sauvegardes | [Connexion DoubleZero Mainnet-Beta](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Validateur Solana Testnet, y compris les sauvegardes | [Connexion DoubleZero Testnet](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | -| Non-Validateurs - RPC, et autres. | [Connexion DoubleZero Mainnet-Beta et Testnet](Permissioned%20Connection.md) | +| Validateur Solana Mainnet, y compris les sauvegardes | [Connexion DoubleZero Mainnet-Beta]() | +| Validateur Solana Testnet, y compris les sauvegardes | [Connexion DoubleZero Testnet]() | +| Non-Validateurs - RPCs et autres. | [Connexion DoubleZero Mainnet-Beta et Testnet](Permissioned%20Connection.md) | Shelby -| Cas d'Utilisation | Prochaine Étape | +| Cas d'utilisation | Étape suivante | |-------------|---------| -| RPC Shelby Testnet et Nœuds de Stockage | [Connexion DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | +| RPCs et nœuds de stockage Shelby Testnet | [Connexion DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | Nouveau Tenant -| Cas d'Utilisation | Prochaine Étape | +| Cas d'utilisation | Étape suivante | -------------|---------| -| Vous avez un nouveau protocole, une application ou un autre cas d'utilisation pour tirer parti de DoubleZero | [Ajouter un Nouveau Tenant](New%20Tenant.md) | +| Vous avez un nouveau protocole, une application ou un autre cas d'utilisation pour tirer parti de DoubleZero | [Ajouter un nouveau Tenant](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.it.md b/docs/tenant.it.md index ac26d5c..ab0c323 100644 --- a/docs/tenant.it.md +++ b/docs/tenant.it.md @@ -1,27 +1,29 @@ -# Tenant -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Scegli il tenant e il percorso di connessione DoubleZero più adatto al tuo caso d'uso — validatori Solana, RPC, Shelby o un nuovo protocollo. +--- +# Tenant -I tenant su DoubleZero sono gruppi di utenti con profili e requisiti di connessione simili, che tipicamente rappresentano diversi ecosistemi blockchain, livelli di trasferimento dati o sistemi distribuiti. +I tenant su DoubleZero sono gruppi di utenti con profili e requisiti di connessione simili, che rappresentano tipicamente diversi ecosistemi blockchain, livelli di trasferimento dati o sistemi distribuiti. -Esamina la tabella seguente e scegli il caso d'uso che descrive meglio come intendi interagire con la rete DoubleZero. +Esamina la tabella seguente e scegli il caso d'uso che meglio descrive come intendi interagire con la rete DoubleZero. Solana -| Caso d'Uso | Prossimo Passo | +| Caso d'uso | Prossimo passo | |-------------|---------| -| Validatore Solana Mainnet, inclusi i backup | [Connessione DoubleZero Mainnet-Beta](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Validatore Solana Testnet, inclusi i backup | [Connessione DoubleZero Testnet](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | +| Validatore Solana Mainnet, inclusi i backup | [Connessione DoubleZero Mainnet-Beta]() | +| Validatore Solana Testnet, inclusi i backup | [Connessione DoubleZero Testnet]() | | Non-Validatori - RPC e altri. | [Connessione DoubleZero Mainnet-Beta e Testnet](Permissioned%20Connection.md) | Shelby -| Caso d'Uso | Prossimo Passo | +| Caso d'uso | Prossimo passo | |-------------|---------| -| RPC Shelby Testnet e Nodi di Storage | [Connessione DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | +| RPC e nodi di archiviazione Shelby Testnet | [Connessione DoubleZero Testnet](Shelby%20Permissioned%20Connection.md) | Nuovo Tenant -| Caso d'Uso | Prossimo Passo | +| Caso d'uso | Prossimo passo | -------------|---------| -| Hai un nuovo protocollo, applicazione o altro caso d'uso per sfruttare DoubleZero | [Aggiungi un Nuovo Tenant](New%20Tenant.md) | +| Hai un nuovo protocollo, applicazione o altro caso d'uso per sfruttare DoubleZero | [Aggiungi un nuovo tenant](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.ja.md b/docs/tenant.ja.md index b50f250..daab1a5 100644 --- a/docs/tenant.ja.md +++ b/docs/tenant.ja.md @@ -1,27 +1,29 @@ -# テナント -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: DoubleZeroのテナントと接続パスを選択してください。Solanaバリデーター、RPC、Shelby、または新しいプロトコルなど、ユースケースに合ったものをお選びください。 +--- +# テナント -DoubleZeroのテナントは、類似したプロフィールと接続要件を持つユーザーグループで、通常は異なるブロックチェーンエコシステム、データ転送レイヤー、または分散システムを表しています。 +DoubleZeroのテナントとは、類似したプロファイルと接続要件を持つユーザーのグループであり、通常は異なるブロックチェーンエコシステム、データ転送レイヤー、または分散システムを表します。 -以下の表を確認し、DoubleZeroネットワークとの連携方法を最もよく表すユースケースを選択してください。 +以下の表を確認し、DoubleZero Networkとの連携方法として最も適切なユースケースを選択してください。 Solana | ユースケース | 次のステップ | |-------------|---------| -| Solanaメインネットバリデーター(バックアップ含む) | [DoubleZero Mainnet-Beta接続](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Solanaテストネットバリデーター(バックアップ含む) | [DoubleZero Testnet接続](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | -| 非バリデーター - RPC等 | [DoubleZero Mainnet-BetaおよびTestnet接続](Permissioned%20Connection.md) | +| Solana Mainnetバリデーター(バックアップを含む) | [DoubleZero Mainnet-Beta接続]() | +| Solana Testnetバリデーター(バックアップを含む) | [DoubleZero Testnet接続]() | +| 非バリデーター - RPC、その他 | [DoubleZero Mainnet-BetaおよびTestnet接続](Permissioned%20Connection.md) | Shelby | ユースケース | 次のステップ | |-------------|---------| -| Shelby Testnet RPCおよびストレージノード | [DoubleZero Testnet接続](Shelby%20Permissioned%20Connection.md) | +| Shelby TestnetのRPCおよびストレージノード | [DoubleZero Testnet接続](Shelby%20Permissioned%20Connection.md) | 新規テナント | ユースケース | 次のステップ | -------------|---------| -| DoubleZeroを活用する新しいプロトコル、アプリケーション、またはその他のユースケース | [新規テナントの追加](New%20Tenant.md) | +| DoubleZeroを活用する新しいプロトコル、アプリケーション、またはその他のユースケースをお持ちの場合 | [新規テナントの追加](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.ko.md b/docs/tenant.ko.md index 1fda27b..9db544b 100644 --- a/docs/tenant.ko.md +++ b/docs/tenant.ko.md @@ -1,27 +1,29 @@ -# 테넌트 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 사용 사례에 맞는 DoubleZero 테넌트와 연결 경로를 선택하세요 — Solana 검증자, RPC, Shelby 또는 새로운 프로토콜. +--- +# 테넌트 -DoubleZero의 테넌트는 유사한 프로필과 연결 요구사항을 가진 사용자 그룹으로, 일반적으로 서로 다른 블록체인 에코시스템, 데이터 전송 레이어 또는 분산 시스템을 나타냅니다. +DoubleZero의 테넌트는 유사한 프로필과 연결 요구 사항을 가진 사용자 그룹으로, 일반적으로 서로 다른 블록체인 생태계, 데이터 전송 계층 또는 분산 시스템을 나타냅니다. -아래 표를 살펴보고 DoubleZero 네트워크와 상호작용하려는 방식을 가장 잘 설명하는 사용 사례를 선택하세요. +아래 표를 확인하고, DoubleZero 네트워크와 상호작용할 방식을 가장 잘 설명하는 사용 사례를 선택하세요. Solana | 사용 사례 | 다음 단계 | |-------------|---------| -| 백업을 포함한 Solana 메인넷 검증자 | [DoubleZero Mainnet-Beta 연결](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| 백업을 포함한 Solana 테스트넷 검증자 | [DoubleZero Testnet 연결](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | -| 비검증자 - RPC 및 기타 | [DoubleZero Mainnet-Beta 및 Testnet 연결](Permissioned%20Connection.md) | +| 백업을 포함한 Solana 메인넷 검증자 | [DoubleZero Mainnet-Beta 연결]() | +| 백업을 포함한 Solana 테스트넷 검증자 | [DoubleZero 테스트넷 연결]() | +| 비검증자 - RPC 및 기타 | [DoubleZero Mainnet-Beta 및 테스트넷 연결](Permissioned%20Connection.md) | Shelby | 사용 사례 | 다음 단계 | |-------------|---------| -| Shelby 테스트넷 RPC 및 스토리지 노드 | [DoubleZero Testnet 연결](Shelby%20Permissioned%20Connection.md) | +| Shelby 테스트넷 RPC 및 스토리지 노드 | [DoubleZero 테스트넷 연결](Shelby%20Permissioned%20Connection.md) | -새 테넌트 +새로운 테넌트 | 사용 사례 | 다음 단계 | -------------|---------| -| DoubleZero를 활용할 새 프로토콜, 애플리케이션 또는 기타 사용 사례가 있습니다 | [새 테넌트 추가](New%20Tenant.md) | +| DoubleZero를 활용하기 위한 새로운 프로토콜, 애플리케이션 또는 기타 사용 사례가 있는 경우 | [새로운 테넌트 추가](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.md b/docs/tenant.md index 425b2c5..ede07fb 100644 --- a/docs/tenant.md +++ b/docs/tenant.md @@ -12,8 +12,8 @@ Solana | Use Case | Next Stop | |-------------|---------| -| Solana Mainnet Validator, including backups | [DoubleZero Mainnet-Beta Connection](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Solana Testnet Validator, including backups | [DoubleZero Testnet Connection](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | +| Solana Mainnet Validator, including backups | [DoubleZero Mainnet-Beta Connection]() | +| Solana Testnet Validator, including backups | [DoubleZero Testnet Connection]() | | Non-Validators - RPCs, and others. | [DoubleZero Mainnet-Beta and Testnet Connection](Permissioned%20Connection.md) | Shelby diff --git a/docs/tenant.pt.md b/docs/tenant.pt.md index 19929ab..5cb2158 100644 --- a/docs/tenant.pt.md +++ b/docs/tenant.pt.md @@ -1,8 +1,10 @@ -# Tenants -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Escolha o tenant e o caminho de conexão DoubleZero que corresponde ao seu caso de uso — validadores Solana, RPCs, Shelby ou um novo protocolo. +--- +# Tenants -Os Tenants no DoubleZero são grupos de usuários com perfis e requisitos de conexão semelhantes, geralmente representando diferentes ecossistemas de blockchain, camadas de transferência de dados ou sistemas distribuídos. +Tenants no DoubleZero são grupos de usuários com perfis e requisitos de conexão semelhantes, normalmente representando diferentes ecossistemas de blockchain, camadas de transferência de dados ou sistemas distribuídos. Examine a tabela abaixo e escolha o caso de uso que melhor descreve como você planeja interagir com a Rede DoubleZero. @@ -10,8 +12,8 @@ Solana | Caso de Uso | Próximo Passo | |-------------|---------| -| Validador Mainnet Solana, incluindo backups | [Conexão DoubleZero Mainnet-Beta](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Validador Testnet Solana, incluindo backups | [Conexão DoubleZero Testnet](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | +| Validador Solana Mainnet, incluindo backups | [Conexão DoubleZero Mainnet-Beta]() | +| Validador Solana Testnet, incluindo backups | [Conexão DoubleZero Testnet]() | | Não-Validadores - RPCs e outros. | [Conexão DoubleZero Mainnet-Beta e Testnet](Permissioned%20Connection.md) | Shelby @@ -24,4 +26,4 @@ Novo Tenant | Caso de Uso | Próximo Passo | -------------|---------| -| Você tem um novo protocolo, aplicação ou outro caso de uso para aproveitar o DoubleZero | [Adicionar um Novo Tenant](New%20Tenant.md) | +| Você tem um novo protocolo, aplicação ou outro caso de uso para utilizar o DoubleZero | [Adicionar Um Novo Tenant](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/tenant.zh.md b/docs/tenant.zh.md index 0f58ee6..c95918a 100644 --- a/docs/tenant.zh.md +++ b/docs/tenant.zh.md @@ -1,27 +1,29 @@ -# 租户 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 选择与您的使用场景相匹配的 DoubleZero 租户和连接路径 — Solana 验证者、RPC、Shelby 或新协议。 +--- +# 租户 -DoubleZero上的租户是具有相似配置和连接需求的用户群组,通常代表不同的区块链生态系统、数据传输层或分布式系统。 +DoubleZero 上的租户是具有相似配置和连接需求的用户组,通常代表不同的区块链生态系统、数据传输层或分布式系统。 -请查看下表,选择最能描述您计划如何与DoubleZero网络交互的用例。 +请查看下表,选择最能描述您计划如何与 DoubleZero 网络交互的使用场景。 Solana -| 用例 | 下一步 | +| 使用场景 | 下一步 | |-------------|---------| -| Solana主网验证器,包括备份 | [DoubleZero主网Beta连接](https://docs.malbeclabs.com/DZ%20Mainnet-beta%20Connection/) | -| Solana测试网验证器,包括备份 | [DoubleZero测试网连接](https://docs.malbeclabs.com/DZ%20Testnet%20Connection/) | -| 非验证器 - RPC及其他 | [DoubleZero主网Beta和测试网连接](Permissioned%20Connection.md) | +| Solana 主网验证者,包括备份节点 | [DoubleZero Mainnet-Beta 连接]() | +| Solana 测试网验证者,包括备份节点 | [DoubleZero 测试网连接]() | +| 非验证者 - RPC 及其他 | [DoubleZero Mainnet-Beta 和测试网连接](Permissioned%20Connection.md) | Shelby -| 用例 | 下一步 | +| 使用场景 | 下一步 | |-------------|---------| -| Shelby测试网RPC和存储节点 | [DoubleZero测试网连接](Shelby%20Permissioned%20Connection.md) | +| Shelby 测试网 RPC 和存储节点 | [DoubleZero 测试网连接](Shelby%20Permissioned%20Connection.md) | 新租户 -| 用例 | 下一步 | +| 使用场景 | 下一步 | -------------|---------| -| 您有新协议、应用程序或其他用例希望利用DoubleZero | [添加新租户](New%20Tenant.md) | +| 您有新的协议、应用程序或其他希望利用 DoubleZero 的使用场景 | [添加新租户](New%20Tenant.md) | \ No newline at end of file diff --git a/docs/troubleshooting.es.md b/docs/troubleshooting.es.md index 3703efb..72d0054 100644 --- a/docs/troubleshooting.es.md +++ b/docs/troubleshooting.es.md @@ -1,38 +1,42 @@ -# Solución de Problemas -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Diagnostique problemas comunes de conexión con DoubleZero con comandos de referencia, salidas esperadas y dónde obtener soporte adicional. +--- +# Solución de problemas -Esta guía cubre una variedad de problemas y está en continuo desarrollo. Si completa la guía puede buscar soporte adicional en el Discord de [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). +Esta guía cubrirá una variedad de problemas y se actualiza continuamente. Si completa la guía, puede buscar soporte adicional en el discord de [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). -## Comandos Comunes y Salidas +## Comandos y salidas comunes -Para comenzar, examine la salida de los siguientes comandos y sus salidas esperadas. Estos le ayudarán en una solución de problemas más detallada. -Si abre un ticket, es posible que le pidan sus salidas. +Para comenzar, examine la salida de los siguientes comandos y su salida esperada. Estos le ayudarán en una solución de problemas más detallada. +Si abre un ticket, es posible que se le solicite su salida. -#### 1. Verificar Versión +#### 1. Verificar versión Comando: `doublezero --version` -Salida de Ejemplo: +Salida de ejemplo: ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) -#### 2. Verificar Dirección DoubleZero +#### 2. Verificar la dirección de DoubleZero Comando: `doublezero address` -Salida de Ejemplo: +Salida de ejemplo: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () -#### 3. Verificar su Pase de Acceso +#### 3. Verificar su Access Pass -Pubkey de ejemplo: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` reemplácela con su pubkey al ejecutar el comando. +Clave pública de ejemplo: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` reemplácela con su clave pública al ejecutar el comando. Comando: @@ -42,39 +46,42 @@ Salida: [nota: usamos `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTww ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. Verificar Créditos del Ledger DoubleZero +[comment]: # () +#### 4. Verificar los créditos del libro mayor de DoubleZero Comando: `doublezero balance` -Salida de Ejemplo: +Salida de ejemplo: ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) -#### 5. Verificar Estado de Conexión +#### 5. Verificar el estado de la conexión Comando: `doublezero status` -Salida de Ejemplo: +Salida de ejemplo: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) + -#### 6. Verificar Latencia +#### 6. Verificar la latencia Comando: `doublezero latency` -Salida de Ejemplo: +Salida de ejemplo: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -85,13 +92,14 @@ Salida de Ejemplo: 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () -# Ejemplos de Solución de Problemas +# Ejemplos de solución de problemas Ahora que hemos examinado las salidas básicas y lo que se espera en un despliegue saludable, podemos examinar algunos ejemplos comunes de solución de problemas. -### Problema: ❌ Error al crear usuario +### Problema: ❌ Error creating user -Este problema generalmente está relacionado con una discrepancia entre el par pubkey/IP esperado y el par pubkey/IP con el que el usuario intenta acceder a DoubleZero. +Este problema generalmente está relacionado con una discrepancia entre el par clave pública/IP esperado y el par clave pública/IP con el que el usuario está intentando acceder a DoubleZero. **Síntomas:** - Al conectarse con `doublezero connect ibrl` el usuario encuentra `❌ Error creating user` @@ -102,7 +110,7 @@ Este problema generalmente está relacionado con una discrepancia entre el par p `doublezero address` - Salida de Ejemplo: + Salida de ejemplo: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` @@ -110,18 +118,18 @@ Este problema generalmente está relacionado con una discrepancia entre el par p `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` - Salida de Ejemplo: + Salida de ejemplo: ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - La pubkey de `doublezero address` debe coincidir con la pubkey user_payer y la dirección IP desde la que intenta conectarse debe coincidir con la IP en el Pase de Acceso. - `doublezero address` proviene del archivo id.json en `~/.config/doublezero/` por defecto. Consulte el [paso 6 aquí](https://docs.malbeclabs.com/setup/) - -3. Si lo anterior parece correcto y está obteniendo un error al conectarse, o si el mapeo anterior es incorrecto, contacte a soporte en [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) + La clave pública de `doublezero address` debe coincidir con la clave pública de user_payer y la dirección IP desde la que está intentando conectarse debe coincidir con la IP en el Access-Pass. + `doublezero address` se obtiene del archivo id.json en ~/.config/doublezero/ por defecto. Consulte el [paso 6 aquí]() + +3. Si lo anterior parece correcto y está obteniendo un error al conectarse, o si el mapeo anterior es incorrecto, por favor contacte con soporte en [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) -### Problema: ❌ Error al aprovisionar servicio: cosas malformadas: no se pueden aprovisionar múltiples túneles al mismo tiempo +### Problema: ❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time Este error indica que un dispositivo ya está conectado a DoubleZero. **Síntomas:** @@ -134,76 +142,207 @@ Este error indica que un dispositivo ya está conectado a DoubleZero. Salida: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `up` indica una conexión saludable. -3. El error aparece porque ya hay un túnel activo hacia DoubleZero con la IP DoubleZero específica en esta máquina. +2. -`up`- indica una conexión saludable. +3. El error aparece porque un túnel hacia DoubleZero con la IP específica de DoubleZero ya está activo en esta máquina. - Este error se encuentra con frecuencia después de una actualización del cliente DoubleZero. Las actualizaciones de DoubleZero reinician automáticamente el servicio doublezerod y le reconectarán si estaba conectado antes del reinicio del servicio. + Este error se encuentra frecuentemente después de una actualización del cliente DoubleZero. Las actualizaciones de DoubleZero reinician automáticamente el servicio doublezerod y lo reconectarán si estaba conectado antes del reinicio del servicio. -### Problema: El estado de DoubleZero es desconocido o caído -Este problema generalmente está relacionado con que el túnel GRE se activó correctamente entre el servidor y el Dispositivo DoubleZero, pero un firewall impide el establecimiento de la sesión BGP. Por ello no está recibiendo rutas de la red ni enviando tráfico a través de DoubleZero. +### Problema: El estado de DoubleZero es unknown o down +Este problema a menudo está relacionado con que el túnel GRE se activó exitosamente entre el servidor y el dispositivo DoubleZero, pero un firewall está impidiendo el establecimiento de la sesión BGP. Debido a esto, no está recibiendo rutas de la red ni enviando tráfico a través de DoubleZero. **Síntomas:** - `doublezero connect ibrl` fue exitoso. Sin embargo, `doublezero status` devuelve `down` o `unknown` + ``` + doublezero connect ibrl + DoubleZero Service Provisioning + 🔗 Start Provisioning User... + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + 🔍 Provisioning User for IP: 111.11.11.11 + User account created + Connected to device: nyc-dz001 + The user has been successfully activated + Service provisioned with status: ok + ✅ User Provisioned + ``` + + ```bash + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet + ``` **Soluciones:** 1. ¡Verifique las reglas de su firewall! - DoubleZero utiliza el espacio de direcciones link-local: 169.254.0.0/16 para las interfaces del túnel GRE entre su máquina y el Dispositivo DoubleZero. 169.254.0.0/16 es típicamente espacio "no enrutable" y por lo tanto las buenas prácticas de seguridad recomendarán bloquear las comunicaciones hacia/desde este espacio. Necesitará permitir una regla en su firewall que permita que src 169.254.0.0/16 se comunique con dst 169.254.0.0/16 en el puerto tcp 179. Esa regla debe colocarse por encima de cualquier regla que deniegue tráfico a 169.254.0.0/16. + DoubleZero utiliza el espacio de direcciones link local: 169.254.0.0/16 para las interfaces del túnel GRE entre su máquina y el dispositivo DoubleZero. 169.254.0.0/16 es típicamente un espacio "no enrutable" y, por lo tanto, las buenas prácticas de seguridad recomendarán bloquear las comunicaciones hacia/desde este espacio. Necesitará permitir una regla en su firewall que habilite la comunicación desde src 169.254.0.0/16 hacia dst 169.254.0.0/16 en el puerto tcp 179. Esa regla deberá colocarse por encima de cualquier regla que deniegue el tráfico hacia 169.254.0.0/16. - En un firewall como ufw puede ejecutar `sudo ufw status` para ver las reglas del firewall. + En un firewall como ufw puede ejecutar `sudo ufw status` para ver las reglas del firewall y - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` para insertar la regla en la posición . Por ejemplo, si N = 1 insertará esta regla como la primera regla. - `sudo ufw status numbered` le mostrará el orden numérico de las reglas. + Salida de ejemplo que puede ser similar a lo que tendría un validador de Solana. + ``` + To Action From + -- ------ ---- + 22/tcp ALLOW Anywhere + 8899/tcp ALLOW Anywhere + 8000:10000/tcp ALLOW Anywhere + 8000:10000/udp ALLOW Anywhere + 11200:11300/udp ALLOW Anywhere + 11200:11300/tcp ALLOW Anywhere + + To Action From + -- ------ ---- + 10.0.0.0/8 DENY OUT Anywhere + 169.254.0.0/16 DENY OUT Anywhere + 172.16.0.0/12 DENY OUT Anywhere + 192.168.0.0/16 DENY OUT Anywhere + ``` + En la salida anterior se ve que todo el tráfico hacia 169.254.0.0/16, excepto para los puertos especificados, está denegado. + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` para insertar la regla en la posición . Es decir, si N = 1, insertará esta regla como la primera regla. + `sudo ufw status numbered` le mostrará el orden numérico de las reglas. + ### Problema: El dispositivo DoubleZero más cercano ha cambiado -Esto no es un error, pero puede ser una optimización. A continuación se muestra una buena práctica que se puede ejecutar de vez en cuando o automatizar. +Esto no es un error, pero puede ser una optimización. A continuación se muestra una buena práctica que puede ejecutarse de vez en cuando o automatizarse. **Soluciones:** 1. Verifique la latencia al dispositivo más cercano - - Ejecute `doublezero latency` + - ejecute `doublezero latency` + + salida + ``` + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + ``` + observe arriba que el dispositivo más cercano es `dz-ny7-sw01` + + Queremos conectarnos a este dispositivo: 2. Determine si ya está conectado al dispositivo objetivo - - Ejecute `doublezero user list --env testnet | grep 111.11.11.11` reemplazando `111.11.11.11` con la dirección IPv4 pública de su dispositivo conectado a DoubleZero. + - ejecute `doublezero user list --env testnet | grep 111.11.11.11` reemplace `111.11.11.11` con la dirección IPv4 pública de su dispositivo que está conectado a DoubleZero. También puede usar su ID de validador o ID de DoubleZero. + + salida + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + En este ejemplo, ya estamos conectados al dispositivo más cercano. No se necesitan más pasos, podemos detenernos aquí. + + + Consideremos en cambio si la salida fuera + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + Esta sería una conexión subóptima. Consideremos si es necesaria una reconexión. + + Antes de la conexión, verificaremos si el dispositivo tiene túneles de usuario disponibles. + +3. Opcional: examinar la red en busca de dispositivos disponibles + + Con fines educativos primero: + - ejecute `doublezero device list` para obtener una lista completa de dispositivos. Hemos extraído 2 dispositivos como ejemplo para explicar la salida. -3. Opcional: examine la red para ver los dispositivos disponibles - - Ejecute `doublezero device list` para obtener una lista completa de dispositivos. + salida: + ``` + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + ``` + Observe arriba que `ams001-dz002` tiene 69 usuarios y 128 usuarios máximos. Este dispositivo puede agregar 59 usuarios. + + Sin embargo, `dz-fr5-sw01` tiene 0 usuarios y 0 usuarios máximos. No podrá conectarse a este dispositivo. Con un máximo de 0 usuarios, el dispositivo no está aceptando conexiones. + + Ahora regresemos a conectarnos a nuestro dispositivo más cercano. 4. Determine si el dispositivo objetivo tiene una conexión disponible - - Ejecute `doublezero device list | grep dz-ny7-sw01` reemplazando `dz-ny7-sw01` con su dispositivo objetivo. + - ejecute `doublezero device list | grep dz-ny7-sw01` reemplace `dz-ny7-sw01` con su dispositivo objetivo + + salida + ``` + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + ``` + aquí podemos ver que `dz-ny7-sw01` tiene espacio disponible para conexión. -5. Conéctese al Dispositivo DoubleZero más cercano +5. Conectarse al dispositivo DoubleZero más cercano + + Nos desconectaremos y luego nos reconectaremos a DoubleZero. Primero ejecute - `doublezero disconnect` - Luego verifique el estado con + salida + + ``` + DoubleZero Service Provisioning + 🔍 Decommissioning User + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + \ [00:00:00] [##########>-----------------------------] 1/4 deleting user account... 🔍 Deleting User Account for: 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW + 🔍 User Account deleted + ✅ Deprovisioning Complete + ``` + ahora verificamos el estado para confirmar nuestra desconexión con - `doublezero status` - Por último reconéctese con + salida + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | + ``` + Por último nos reconectaremos con - `doublezero connect ibrl` + salida + ``` + DoubleZero Service Provisioning + 🔗 Start Provisioning User... + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + 🔍 Provisioning User for IP: 111.11.11.11 + User account created + Connected to device: dz-ny7-sw01 + Service provisioned with status: ok + ✅ User Provisioned + ``` + observe en la salida anterior que nos `Connected to device: dz-ny7-sw01` este es el resultado deseado de nuestra investigación inicial en el paso 1, donde descubrimos que `dz-ny7-sw01` era el dispositivo con la latencia más baja. + ### Problema: `doublezero status` devuelve algunos campos con N/A -Este problema generalmente está relacionado con una discrepancia entre el daemon y cliente actuales versus el daemon y cliente con los que se estableció el túnel DZ conectado. +Este problema generalmente está relacionado con una discrepancia entre el daemon y cliente actuales, frente al daemon y cliente con los que se estableció el túnel DZ conectado. **Síntomas:** - Al ejecutar `doublezero status` el usuario encuentra `N/A` en algunos campos + + + **Soluciones:** 1. Ejecute `doublezero status` - Note que el `Tunnel status` es `up` y `Network` es `mainnet-beta` pero `Current Device` y `Metro` son `N/A`. Esto indica un túnel abierto en su máquina que no está en su entorno actual. + Ejemplo: + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta + ``` + + Observe en nuestra salida de ejemplo anterior que el `Tunnel status` es `up`. Nuestra `Network` es `mainnet-beta`. Sin embargo, `Current Device` y `Metro` son `N/A` + Esto es indicativo de un túnel abierto en su máquina que no está en su entorno actual. + En este caso, el estado `up`, sin `Current Device` encontrado en `mainnet-beta`, nos revela que ¡nuestro túnel está en testnet! + 2. Cambie su entorno. - Para configurar el CLI `doublezero` y el daemon `doublezerod` para conectarse a **DoubleZero testnet**: + Para rectificar la discrepancia, cambiará su entorno al opuesto del entorno que devuelve `N/A` ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -215,7 +354,7 @@ Este problema generalmente está relacionado con una discrepancia entre el daemo && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - Para configurar el CLI `doublezero` y el daemon `doublezerod` para conectarse a **DoubleZero mainnet-beta**: + Para configurar la CLI del cliente DoubleZero (`doublezero`) y el daemon (`doublezerod`) para conectarse a **DoubleZero mainnet-beta**: ```bash DESIRED_DOUBLEZERO_ENV=mainnet-beta \ @@ -226,13 +365,19 @@ Este problema generalmente está relacionado con una discrepancia entre el daemo && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - + 3. Verifique su estado - Después de cambiar el entorno ejecute: + Después de cambiar de entorno ejecute: ``` doublezero status ``` - Con todos los campos completados ahora está en el entorno correcto. + La salida esperada debería ser similar a: + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet + ``` +Con todos los campos completados, ahora se encuentra en el entorno correcto. \ No newline at end of file diff --git a/docs/troubleshooting.fr.md b/docs/troubleshooting.fr.md index 99368ed..a1bfe81 100644 --- a/docs/troubleshooting.fr.md +++ b/docs/troubleshooting.fr.md @@ -1,16 +1,18 @@ -# Résolution des Problèmes -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Diagnostiquez les problèmes courants de connexion DoubleZero avec des commandes de référence, les sorties attendues et où obtenir une assistance supplémentaire. +--- +# Dépannage -Ce guide couvre une variété de problèmes et est en cours d'élaboration. Si vous avez complété le guide, vous pouvez chercher une aide supplémentaire sur le discord [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). +Ce guide couvre une variété de problèmes et est mis à jour régulièrement. Si vous avez parcouru l'intégralité du guide, vous pouvez obtenir une assistance supplémentaire sur le discord [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). -## Commandes Courantes et Sorties +## Commandes courantes et sorties attendues -Pour commencer, examinez la sortie des commandes suivantes et leur sortie attendue. Elles vous aideront à résoudre les problèmes de manière plus détaillée. +Pour commencer, examinez la sortie des commandes suivantes ainsi que leur sortie attendue. Celles-ci vous aideront dans un dépannage plus détaillé. Si vous ouvrez un ticket, on pourrait vous demander leur sortie. -#### 1. Vérifier la Version +#### 1. Vérifier la version Commande : `doublezero --version` @@ -19,8 +21,9 @@ Exemple de sortie : ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) -#### 2. Vérifier l'Adresse DoubleZero +#### 2. Vérifier l'adresse DoubleZero Commande : `doublezero address` @@ -29,23 +32,24 @@ Exemple de sortie : ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () -#### 3. Vérifier votre Pass d'Accès +#### 3. Vérifier votre Access Pass -Exemple de clé publique : `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` remplacez cela par votre clé publique lors de l'exécution de la commande. +Clé publique d'exemple : `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` remplacez-la par votre clé publique lors de l'exécution de la commande. Commande : `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -Sortie : [notez que nous utilisons `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` pour afficher l'en-tête dans cette sortie] +Sortie : [notez que nous utilisons `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` pour vous montrer l'en-tête dans cette sortie] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. Vérifier les Crédits du Registre DoubleZero +[comment]: # () +#### 4. Vérifier les crédits du registre DoubleZero Commande : `doublezero balance` @@ -54,8 +58,9 @@ Exemple de sortie : ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) -#### 5. Vérifier l'État de la Connexion +#### 5. Vérifier l'état de la connexion Commande : `doublezero status` @@ -63,19 +68,20 @@ Commande : Exemple de sortie : ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) -#### 6. Vérifier la Latence +#### 6. Vérifier la latence Commande : `doublezero latency` Exemple de sortie : ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -86,13 +92,14 @@ Exemple de sortie : 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () -# Exemples de Résolution des Problèmes -Maintenant que nous avons examiné les sorties de base et ce qui est attendu dans un déploiement sain, nous pouvons examiner quelques exemples courants de résolution de problèmes. +# Exemples de dépannage +Maintenant que nous avons examiné les sorties de base et ce qui est attendu dans un déploiement sain, nous pouvons examiner quelques exemples courants de dépannage. -### Problème : ❌ Erreur lors de la création d'un utilisateur +### Problème : ❌ Error creating user -Ce problème est généralement lié à une discordance entre la combinaison clé publique/IP attendue et la combinaison clé publique/IP avec laquelle l'utilisateur essaie d'accéder à DoubleZero. +Ce problème est généralement lié à une discordance entre la paire clé publique/IP attendue et la paire clé publique/IP avec laquelle l'utilisateur essaie d'accéder à DoubleZero. **Symptômes :** - Lors de la connexion avec `doublezero connect ibrl`, l'utilisateur rencontre `❌ Error creating user` @@ -115,15 +122,15 @@ Ce problème est généralement lié à une discordance entre la combinaison cl ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - La clé publique de `doublezero address` doit correspondre à la clé publique user_payer et l'adresse IP depuis laquelle vous essayez de vous connecter doit correspondre à l'IP dans le Pass d'Accès. - `doublezero address` est sourcé depuis le fichier id.json dans ~/.config/doublezero/ par défaut. Voir l'[étape 6 ici](https://docs.malbeclabs.com/setup/) - -3. Si ce qui précède semble correct et que vous obtenez une erreur lors de la connexion, ou si le mappage ci-dessus est incorrect, veuillez contacter le support sur [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) + La clé publique de `doublezero address` doit correspondre à la clé publique user_payer et l'adresse IP depuis laquelle vous essayez de vous connecter doit correspondre à l'IP dans l'Access-Pass. + `doublezero address` est sourcée depuis le fichier id.json dans ~/.config/doublezero/ par défaut. Voir l'[étape 6 ici]() + +3. Si ce qui précède semble correct et que vous obtenez une erreur lors de la connexion, ou si la correspondance ci-dessus est incorrecte, veuillez contacter le support sur [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) -### Problème : ❌ Erreur de provisionnement du service : malformed stuff: cannot provision multiple tunnels at the same time -Cette erreur indique qu'un dispositif est déjà connecté à DoubleZero. +### Problème : ❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time +Cette erreur signifie qu'un appareil est déjà connecté à DoubleZero. **Symptômes :** - L'utilisateur essaie de se connecter à DoubleZero @@ -135,22 +142,22 @@ Cette erreur indique qu'un dispositif est déjà connecté à DoubleZero. Sortie : ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. -`up`- indique une connexion saine. +2. -`up`- signifie une connexion saine. 3. L'erreur apparaît parce qu'un tunnel vers DoubleZero avec l'IP DoubleZero spécifique est déjà actif sur cette machine. - Cette erreur est souvent rencontrée après une mise à niveau du client DoubleZero. Les mises à niveau DoubleZero redémarrent automatiquement le service doublezerod et vous reconnecteront si vous étiez connecté avant le redémarrage du service. + Cette erreur est souvent rencontrée après une mise à jour du client DoubleZero. Les mises à jour de DoubleZero redémarrent automatiquement le service doublezerod et vous reconnecteront si vous étiez connecté avant le redémarrage du service. -### Problème : Le statut DoubleZero est inconnu ou down -Ce problème est souvent lié au tunnel GRE qui a été activé avec succès entre le serveur et le DoubleZero Device, mais un pare-feu empêche l'établissement de la session BGP. À cause de cela, vous ne recevez pas de routes depuis le réseau ou n'envoyez pas de trafic via DoubleZero. +### Problème : Le statut DoubleZero est unknown ou down +Ce problème est souvent lié au tunnel GRE qui a été activé avec succès entre le serveur et l'appareil DoubleZero, mais un pare-feu empêche l'établissement de la session BGP. De ce fait, vous ne recevez pas de routes du réseau et n'envoyez pas de trafic via DoubleZero. **Symptômes :** - `doublezero connect ibrl` a réussi. Cependant, `doublezero status` retourne `down` ou `unknown` ``` - doublezero connect ibrl + doublezero connect ibrl DoubleZero Service Provisioning 🔗 Start Provisioning User... Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` @@ -163,18 +170,18 @@ Ce problème est souvent lié au tunnel GRE qui a été activé avec succès ent ``` ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` **Solutions :** 1. Vérifiez vos règles de pare-feu ! - DoubleZero utilise l'espace d'adresses link-local : 169.254.0.0/16 pour les interfaces de tunnel GRE entre votre machine et le DoubleZero Device. 169.254.0.0/16 est typiquement un espace « non routable » et donc les bonnes pratiques de sécurité recommandent de bloquer les communications vers/depuis cet espace. Vous devrez autoriser une règle dans votre pare-feu qui permet à src 169.254.0.0/16 de communiquer avec dst 169.254.0.0/16 sur le port tcp 179. Cette règle devra être placée au-dessus de toutes les règles qui refusent le trafic vers 169.254.0.0/16. + DoubleZero utilise l'espace d'adresses link-local : 169.254.0.0/16 pour les interfaces de tunnel GRE entre votre machine et l'appareil DoubleZero. 169.254.0.0/16 est typiquement un espace « non-routable » et les bonnes pratiques de sécurité recommandent de bloquer les communications vers/depuis cet espace. Vous devrez autoriser une règle dans votre pare-feu qui permet à la source 169.254.0.0/16 de communiquer avec la destination 169.254.0.0/16 sur le port TCP 179. Cette règle devra être placée au-dessus de toute règle qui refuse le trafic vers 169.254.0.0/16. - Dans un pare-feu comme ufw, vous pouvez exécuter `sudo ufw status` pour afficher les règles du pare-feu et + Dans un pare-feu comme ufw, vous pouvez exécuter `sudo ufw status` pour visualiser les règles du pare-feu et - Exemple de sortie qui pourrait être similaire à ce qu'un validateur Solana aurait. + Exemple de sortie qui pourrait ressembler à ce qu'un validateur Solana aurait. ``` To Action From -- ------ ---- @@ -193,83 +200,83 @@ Ce problème est souvent lié au tunnel GRE qui a été activé avec succès ent 192.168.0.0/16 DENY OUT Anywhere ``` - Dans la sortie ci-dessus, vous voyez que tout le trafic vers 169.254.0.0/16, sauf pour les ports spécifiés, est refusé. - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` pour insérer la règle à la position . c'est-à-dire que si N = 1, vous insérerez cette règle comme première règle. + Dans la sortie ci-dessus, vous voyez que tout le trafic vers 169.254.0.0/16, à l'exception des ports spécifiés, est refusé. + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` pour insérer la règle à la position . Par exemple, si N = 1, vous insérerez cette règle comme première règle. `sudo ufw status numbered` vous montrera l'ordre numérique des règles. + +### Problème : L'appareil DoubleZero le plus proche a changé -### Problème : Le dispositif DoubleZero le plus proche a changé - -Ce n'est pas une erreur, mais cela peut être une optimisation. Voici une bonne pratique qui peut être exécutée de temps en temps ou automatisée. +Ce n'est pas une erreur, mais cela peut être une optimisation. Ci-dessous se trouve une bonne pratique qui peut être exécutée de temps en temps, ou automatisée. **Solutions :** -1. Vérifiez la latence vers le dispositif le plus proche +1. Vérifiez la latence vers l'appareil le plus proche - exécutez `doublezero latency` sortie ``` - pubkey | code | ip | min | max | avg | reachable - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true - ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true - 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true - 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true - FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true ``` - notez ci-dessus que le dispositif le plus proche est `dz-ny7-sw01` + notez ci-dessus que l'appareil le plus proche est `dz-ny7-sw01` - Nous voulons nous connecter à ce dispositif. : + Nous voulons nous connecter à cet appareil. : -2. Déterminez si vous êtes déjà connecté au dispositif cible - - exécutez `doublezero user list --env testnet | grep 111.11.11.11` remplacez `111.11.11.11` par l'adresse IPv4 publique de votre dispositif connecté à DoubleZero. Vous pouvez également utiliser votre ID de validateur ou l'ID doublezero. +2. Déterminez si vous êtes déjà connecté à l'appareil cible + - exécutez `doublezero user list --env testnet | grep 111.11.11.11` remplacez `111.11.11.11` par l'adresse IPv4 publique de votre appareil connecté à DoubleZero. Vous pouvez également utiliser votre ID de validateur ou votre ID DoubleZero. sortie ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - Dans cet exemple, nous sommes déjà connectés au dispositif le plus proche. Plus d'étapes ne sont nécessaires, nous pouvons nous arrêter ici. + Dans cet exemple, nous sommes déjà connectés à l'appareil le plus proche. Aucune étape supplémentaire n'est nécessaire, nous pouvons nous arrêter ici. Considérons plutôt si la sortie était ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - Ce serait une connexion sous-optimale. Considérons si une reconnexion est nécessaire. + Ce serait une connexion sous-optimale. Voyons si une reconnexion est nécessaire. - Avant la connexion, nous vérifierons si le dispositif a des tunnels utilisateur disponibles. + Avant la connexion, nous vérifierons si l'appareil dispose de tunnels utilisateur disponibles. -3. Optionnel : examiner le réseau pour les dispositifs disponibles +3. Optionnel : examiner le réseau pour les appareils disponibles - À des fins éducatives, nous allons d'abord : - - exécuter `doublezero device list` pour une liste complète des dispositifs. Nous avons extrait 2 dispositifs comme exemple pour expliquer la sortie. + À des fins pédagogiques, nous allons d'abord : + - exécuter `doublezero device list` pour obtenir la liste complète des appareils. Nous avons extrait 2 appareils comme exemple pour expliquer la sortie. sortie : ``` - account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner - GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp - 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - Notez ci-dessus que `ams001-dz002` a 69 utilisateurs et 128 utilisateurs maximum. Ce dispositif peut accueillir 59 utilisateurs supplémentaires. + Notez ci-dessus que `ams001-dz002` a 69 utilisateurs et 128 utilisateurs maximum. Cet appareil peut accueillir 59 utilisateurs supplémentaires. - Cependant, `dz-fr5-sw01` a 0 utilisateurs et 0 utilisateurs maximum. Vous ne pourrez pas vous connecter à ce dispositif. Avec un max_users de 0, le dispositif n'accepte aucune connexion. + Cependant, `dz-fr5-sw01` a 0 utilisateur et 0 utilisateur maximum. Vous ne pourrez pas vous connecter à cet appareil. Avec un maximum de 0 utilisateurs, l'appareil n'accepte aucune connexion. - Revenons maintenant à la connexion à notre dispositif le plus proche. + Revenons maintenant à la connexion à notre appareil le plus proche. -4. Déterminez si le dispositif cible a une connexion disponible - - exécutez `doublezero device list | grep dz-ny7-sw01` remplacez `dz-ny7-sw01` par votre dispositif cible +4. Déterminez si l'appareil cible a une connexion disponible + - exécutez `doublezero device list | grep dz-ny7-sw01` remplacez `dz-ny7-sw01` par votre appareil cible sortie ``` - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - ici nous pouvons voir que `dz-ny7-sw01` dispose d'espace disponible pour la connexion. + ici nous pouvons voir que `dz-ny7-sw01` dispose d'espace disponible pour une connexion. -5. Se connecter au dispositif DoubleZero le plus proche +5. Se connecter à l'appareil DoubleZero le plus proche - Nous allons nous déconnecter, puis nous reconnecter à doublezero. + Nous allons nous déconnecter, puis nous reconnecter à DoubleZero. - Exécutez d'abord + D'abord exécutez - `doublezero disconnect` sortie @@ -288,10 +295,10 @@ Ce n'est pas une erreur, mais cela peut être une optimisation. Voici une bonne sortie ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type - disconnected | no session data | | | | | + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | ``` - Enfin nous nous reconnecterons avec + Enfin, nous nous reconnectons avec - `doublezero connect ibrl` sortie @@ -301,15 +308,15 @@ Ce n'est pas une erreur, mais cela peut être une optimisation. Voici une bonne Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` 🔍 Provisioning User for IP: 111.11.11.11 User account created - Connected to device: dz-ny7-sw01 + Connected to device: dz-ny7-sw01 Service provisioned with status: ok ✅ User Provisioned ``` - notez dans la sortie ci-dessus que nous avons `Connected to device: dz-ny7-sw01` c'est le résultat souhaité de notre enquête initiale à l'étape 1, où nous avons découvert que `dz-ny7-sw01` était le dispositif avec la latence la plus faible. + notez dans la sortie ci-dessus que nous avons `Connected to device: dz-ny7-sw01`, c'est le résultat souhaité suite à notre investigation initiale à l'étape 1, où nous avions découvert que `dz-ny7-sw01` était l'appareil avec la latence la plus faible. ### Problème : `doublezero status` retourne certains champs avec N/A -Ce problème est généralement lié à une discordance entre le daemon et le client actuels, par rapport au daemon et au client avec lesquels le tunnel DZ connecté a été établi. +Ce problème est généralement lié à une discordance entre le daemon et le client actuels, et le daemon et le client avec lesquels le tunnel DZ connecté a été établi. **Symptômes :** - Lors de l'exécution de `doublezero status`, l'utilisateur rencontre `N/A` dans certains champs @@ -331,11 +338,11 @@ Ce problème est généralement lié à une discordance entre le daemon et le cl Notez dans notre exemple de sortie ci-dessus que le `Tunnel status` est `up`. Notre `Network` est `mainnet-beta`. Cependant, `Current Device` et `Metro` sont `N/A` Cela indique un tunnel ouvert sur votre machine qui n'est pas dans votre environnement actuel. - Dans ce cas, le statut `up`, sans `Current Device` trouvé sur `mainnet-beta` nous révèle que notre tunnel est sur testnet ! - + Dans ce cas, le statut `up`, sans `Current Device` trouvé sur `mainnet-beta`, nous révèle que notre tunnel est sur testnet ! + 2. Changez votre environnement. - Pour rectifier la discordance, vous changerez votre environnement vers l'opposé de l'environnement retournant `N/A` + Afin de rectifier la discordance, vous changerez votre environnement vers l'opposé de l'environnement retournant le `N/A` ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -347,7 +354,7 @@ Ce problème est généralement lié à une discordance entre le daemon et le cl && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - Pour configurer la CLI Client DoubleZero (`doublezero`) et le daemon (`doublezerod`) afin de se connecter au **mainnet-beta DoubleZero** : + Pour configurer le CLI client DoubleZero (`doublezero`) et le daemon (`doublezerod`) pour se connecter à **DoubleZero mainnet-beta** : ```bash DESIRED_DOUBLEZERO_ENV=mainnet-beta \ @@ -358,7 +365,7 @@ Ce problème est généralement lié à une discordance entre le daemon et le cl && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - + 3. Vérifiez votre statut Après avoir changé d'environnement, exécutez : @@ -369,8 +376,8 @@ Ce problème est généralement lié à une discordance entre le daemon et le cl La sortie attendue devrait être similaire à : + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network - up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet - ``` -Avec tous les champs remplis, vous êtes maintenant dans le bon environnement. +Avec tous les champs remplis, vous êtes maintenant dans le bon environnement. \ No newline at end of file diff --git a/docs/troubleshooting.it.md b/docs/troubleshooting.it.md index 05fe677..57482dd 100644 --- a/docs/troubleshooting.it.md +++ b/docs/troubleshooting.it.md @@ -1,16 +1,18 @@ -# Risoluzione dei Problemi -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Diagnostica i problemi comuni di connessione a DoubleZero con comandi di riferimento, output attesi e dove ottenere ulteriore supporto. +--- +# Risoluzione dei problemi -Questa guida copre una varietà di problemi ed è in continuo aggiornamento. Se completi la guida puoi cercare ulteriore supporto nel Discord [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). +Questa guida tratterà una varietà di problemi ed è in continuo aggiornamento. Se completi la guida puoi cercare ulteriore supporto nel discord di [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). -## Comandi Comuni e Output +## Comandi e Output Comuni -Per iniziare, esamina l'output dei seguenti comandi e il loro output atteso. Questi ti aiuteranno nella risoluzione dei problemi più dettagliata. -Se apri un ticket, potrebbe essere richiesto l'output di questi comandi. +Per iniziare, esamina l'output dei seguenti comandi e il loro output atteso. Questi ti assisteranno nella risoluzione dei problemi più dettagliata. +Se apri un ticket, potrebbe esserti chiesto il loro output. -#### 1. Controlla la Versione +#### 1. Verifica Versione Comando: `doublezero --version` @@ -19,8 +21,9 @@ Output di esempio: ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) -#### 2. Controlla l'Indirizzo DoubleZero +#### 2. Verifica Indirizzo DoubleZero Comando: `doublezero address` @@ -29,23 +32,24 @@ Output di esempio: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () -#### 3. Verifica il tuo Pass di Accesso +#### 3. Verifica il tuo Access Pass -Chiave pubblica di esempio: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` - sostituiscila con la tua chiave pubblica quando esegui il comando. +Pubkey di esempio: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` sostituiscilo con la tua pubkey quando esegui il comando. Comando: `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -Output: [nota che usiamo `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` per mostrare l'intestazione in questo output] +Output: [nota che usiamo `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` per mostrarti ora l'intestazione in questo output] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. Controlla i Crediti del Registro DoubleZero +[comment]: # () +#### 4. Verifica Crediti nel Ledger DoubleZero Comando: `doublezero balance` @@ -54,8 +58,9 @@ Output di esempio: ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) -#### 5. Controlla lo Stato della Connessione +#### 5. Verifica Stato della Connessione Comando: `doublezero status` @@ -63,19 +68,20 @@ Comando: Output di esempio: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) -#### 6. Controlla la Latenza +#### 6. Verifica Latenza Comando: `doublezero latency` Output di esempio: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -86,20 +92,21 @@ Output di esempio: 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () # Esempi di Risoluzione dei Problemi -Ora che abbiamo esaminato gli output di base e ciò che è atteso in un deployment sano, possiamo esaminare alcuni esempi comuni di risoluzione dei problemi. +Ora che abbiamo esaminato gli output di base e ciò che ci si aspetta in un deployment sano, possiamo esaminare alcuni esempi comuni di risoluzione dei problemi. ### Problema: ❌ Error creating user -Questo problema è generalmente correlato a una mancata corrispondenza tra la coppia chiave pubblica/IP attesa e quella che l'utente sta cercando di usare per accedere a DoubleZero. +Questo problema è generalmente correlato a una discrepanza tra l'abbinamento pubkey/IP previsto e l'abbinamento pubkey/IP con cui l'utente sta cercando di accedere a DoubleZero. **Sintomi:** -- Quando si connette con `doublezero connect ibrl` l'utente incontra `❌ Error creating user` +- Quando ci si connette con `doublezero connect ibrl` l'utente riscontra `❌ Error creating user` **Soluzioni:** -1. Controlla +1. Verifica `doublezero address` @@ -115,42 +122,42 @@ Questo problema è generalmente correlato a una mancata corrispondenza tra la co ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - La chiave pubblica da `doublezero address` deve corrispondere alla chiave pubblica user_payer e l'indirizzo IP da cui stai cercando di connetterti deve corrispondere all'IP nell'Access-Pass. - `doublezero address` è originato dal file id.json in ~/.config/doublezero/ per impostazione predefinita. Vedi il [passaggio 6 qui](https://docs.malbeclabs.com/setup/) - -3. Se quanto sopra sembra corretto e stai ricevendo un errore durante la connessione, o se la mappatura sopra è errata, contatta il supporto in [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) + La pubkey da `doublezero address` deve corrispondere alla pubkey user_payer e l'indirizzo IP da cui stai cercando di connetterti deve corrispondere all'ip nell'Access-Pass. + `doublezero address` è ricavato dal file id.json in ~/.config/doublezero/ per impostazione predefinita. Vedi il [passaggio 6 qui]() + +3. Se quanto sopra sembra corretto e stai ricevendo un errore durante la connessione o se la mappatura sopra è errata, contatta il supporto su [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) ### Problema: ❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time Questo errore indica che un dispositivo è già connesso a DoubleZero. **Sintomi:** - L'utente cerca di connettersi a DoubleZero -- Viene incontrato `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`. +- Viene riscontrato `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`. **Soluzioni:** -1. Controlla +1. Verifica `doublezero status` Output: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `-up-` indica una connessione sana. -3. L'errore appare perché un tunnel a DoubleZero con il DoubleZero IP specifico è già attivo su questa macchina. +2. -`up`- indica una connessione sana. +3. L'errore appare perché un tunnel verso DoubleZero con lo specifico IP DoubleZero è già attivo su questa macchina. - Questo errore si verifica spesso dopo un aggiornamento del client DoubleZero. Gli aggiornamenti DoubleZero riavviano automaticamente il servizio doublezerod e ti riconnetteranno se eri connesso prima del riavvio del servizio. + Questo errore viene spesso riscontrato dopo un aggiornamento del client DoubleZero. Gli aggiornamenti di DoubleZero riavviano automaticamente il servizio doublezerod e ti riconnetteranno se eri connesso prima del riavvio del servizio. -### Problema: Lo Stato DoubleZero è unknown o down -Questo problema è spesso correlato al tunnel GRE attivato con successo tra il server e il DoubleZero Device, ma un firewall che impedisce l'instaurazione della sessione BGP. Per questo motivo non stai ricevendo route dalla rete né inviando traffico su DoubleZero. +### Problema: Lo stato di DoubleZero è unknown o down +Questo problema è spesso correlato al tunnel GRE che viene attivato con successo tra il server e il Dispositivo DoubleZero, ma un firewall impedisce l'instaurazione della sessione BGP. Per questo motivo non stai ricevendo rotte dalla rete né inviando traffico tramite DoubleZero. **Sintomi:** -- `doublezero connect ibrl` è riuscito. Tuttavia, `doublezero status` restituisce `down` o `unknown` +- `doublezero connect ibrl` ha avuto successo. Tuttavia, `doublezero status` restituisce `down` o `unknown` ``` - doublezero connect ibrl + doublezero connect ibrl DoubleZero Service Provisioning 🔗 Start Provisioning User... Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` @@ -163,16 +170,16 @@ Questo problema è spesso correlato al tunnel GRE attivato con successo tra il s ``` ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` **Soluzioni:** -1. Controlla le tue regole firewall! +1. Controlla le regole del tuo firewall! - DoubleZero utilizza lo spazio degli indirizzi link-local: 169.254.0.0/16 per le interfacce tunnel GRE tra la tua macchina e il DoubleZero Device. 169.254.0.0/16 è tipicamente spazio "non instradabile" e pertanto le buone pratiche di sicurezza raccomandano di bloccare le comunicazioni verso/da questo spazio. Dovrai autorizzare una regola nel tuo firewall che consenta a src 169.254.0.0/16 di comunicare con dst 169.254.0.0/16 sulla porta tcp 179. Quella regola dovrà essere posizionata sopra qualsiasi regola che nega il traffico verso 169.254.0.0/16. + DoubleZero utilizza lo spazio di indirizzi link local: 169.254.0.0/16 per le interfacce del tunnel GRE tra la tua macchina e il Dispositivo DoubleZero. 169.254.0.0/16 è tipicamente spazio "non instradabile" e quindi le buone pratiche di sicurezza raccomandano di bloccare le comunicazioni da/verso questo spazio. Dovrai consentire una regola nel tuo firewall che permetta a src 169.254.0.0/16 di comunicare con dst 169.254.0.0/16 sulla porta tcp 179. Quella regola dovrà essere posizionata sopra qualsiasi regola che nega il traffico verso 169.254.0.0/16. - In un firewall come ufw puoi eseguire `sudo ufw status` per visualizzare le regole del firewall. + In un firewall come ufw puoi eseguire `sudo ufw status` per visualizzare le regole del firewall e Output di esempio che potrebbe essere simile a quello di un validatore Solana. ``` @@ -193,73 +200,105 @@ Questo problema è spesso correlato al tunnel GRE attivato con successo tra il s 192.168.0.0/16 DENY OUT Anywhere ``` - Nell'output sopra vedi che tutto il traffico verso 169.254.0.0/16, eccetto le porte specificate, viene negato. - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` per inserire la regola nella posizione . Cioè, se N = 1 inserirai questa regola come prima regola. + Nell'output sopra vedi che tutto il traffico verso 169.254.0.0/16, eccetto per le porte specificate, è negato. + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` per inserire la regola nella posizione . Ad esempio: se N = 1 allora inserirai questa regola come prima regola. `sudo ufw status numbered` ti mostrerà l'ordinamento numerico delle regole. - + ### Problema: Il dispositivo DoubleZero più vicino è cambiato -Non si tratta di un errore, ma può essere un'ottimizzazione. Di seguito è riportata una best practice che può essere eseguita di tanto in tanto, o automatizzata. +Questo non è un errore, ma può essere un'ottimizzazione. Di seguito è riportata una best practice che può essere eseguita di tanto in tanto, o automatizzata. **Soluzioni:** -1. Controlla la latenza verso il dispositivo più vicino +1. Verifica la latenza verso il dispositivo più vicino - esegui `doublezero latency` output ``` - pubkey | code | ip | min | max | avg | reachable - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true - ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true - 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true - 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true - FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true ``` - nota sopra che il dispositivo più vicino è `dz-ny7-sw01` + nota sopra che il dispositivo più vicino è `dz-ny7-sw01 ` - Vogliamo connetterci a questo dispositivo. + Vogliamo connetterci a questo dispositivo. : 2. Determina se sei già connesso al dispositivo target - - esegui `doublezero user list --env testnet | grep 111.11.11.11` sostituendo `111.11.11.11` con l'indirizzo IPv4 pubblico del tuo dispositivo connesso a DoubleZero. + - esegui `doublezero user list --env testnet | grep 111.11.11.11` sostituisci `111.11.11.11` con l'indirizzo IPv4 pubblico del tuo dispositivo connesso a DoubleZero. Puoi anche usare il tuo validator ID o doublezero ID. output ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + In questo esempio, siamo già connessi al dispositivo più vicino. Non sono necessari ulteriori passaggi, possiamo fermarci qui. + + + Consideriamo invece se l'output fosse stato + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - In questo esempio, siamo già connessi al dispositivo più vicino. Non sono necessari altri passaggi, possiamo fermarci qui. + Questa sarebbe una connessione sub-ottimale. Consideriamo se è necessaria una riconnessione. + + Prima della connessione, verificheremo se il dispositivo ha tunnel utente disponibili. 3. Opzionale: esamina la rete per i dispositivi disponibili - - esegui `doublezero device list` per un elenco completo dei dispositivi. + A scopo educativo prima: + - esegui `doublezero device list` per un elenco completo dei dispositivi. Abbiamo estratto 2 dispositivi come esempio per spiegare l'output. output: ``` - account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner - GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp - 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - Nota che `ams001-dz002` ha 69 utenti e 128 max utenti. Questo dispositivo può aggiungere 59 utenti. + Nota sopra che `ams001-dz002` ha 69 utenti e 128 utenti massimi. Questo dispositivo può aggiungere 59 utenti. + + Tuttavia, `dz-fr5-sw01` ha 0 utenti e 0 utenti massimi. Non potrai connetterti a questo dispositivo. Con un massimo di utenti pari a 0, il dispositivo non accetta connessioni. - Tuttavia, `dz-fr5-sw01` ha 0 utenti e 0 max utenti. Non potrai connetterti a questo dispositivo. + Ora torniamo a connetterci al nostro dispositivo più vicino. 4. Determina se il dispositivo target ha una connessione disponibile - - esegui `doublezero device list | grep dz-ny7-sw01` + - esegui `doublezero device list | grep dz-ny7-sw01` sostituisci `dz-ny7-sw01` con il tuo dispositivo target output ``` - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` + qui possiamo vedere che `dz-ny7-sw01` ha spazio disponibile per la connessione. + +5. Connettiti al Dispositivo DoubleZero più vicino -5. Connettiti al DoubleZero Device più vicino + Ci disconnetteremo e poi ci riconnetteremo a doublezero. Prima esegui - `doublezero disconnect` - poi controlla lo stato con + output + + ``` + DoubleZero Service Provisioning + 🔍 Decommissioning User + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + \ [00:00:00] [##########>-----------------------------] 1/4 deleting user account... 🔍 Deleting User Account for: 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW + 🔍 User Account deleted + ✅ Deprovisioning Complete + ``` + ora verifichiamo lo stato per confermare la nostra disconnessione con - `doublezero status` - poi riconnettiti con + output + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | + ``` + Infine ci riconnetteremo con - `doublezero connect ibrl` output @@ -269,17 +308,20 @@ Non si tratta di un errore, ma può essere un'ottimizzazione. Di seguito è ripo Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` 🔍 Provisioning User for IP: 111.11.11.11 User account created - Connected to device: dz-ny7-sw01 + Connected to device: dz-ny7-sw01 Service provisioned with status: ok ✅ User Provisioned ``` + nota nell'output sopra che ci siamo `Connected to device: dz-ny7-sw01` questo è il risultato desiderato dalla nostra indagine iniziale nel passaggio 1, dove abbiamo scoperto che `dz-ny7-sw01` era il dispositivo con la latenza più bassa. ### Problema: `doublezero status` restituisce alcuni campi con N/A -Questo problema è generalmente correlato a una mancata corrispondenza tra il daemon e il client correnti, rispetto al daemon e al client con cui è stato stabilito il tunnel DZ connesso. +Questo problema è generalmente correlato a una discrepanza tra il daemon e il client correnti, rispetto al daemon e al client con cui il tunnel DZ connesso è stato stabilito. **Sintomi:** -- Quando si esegue `doublezero status` l'utente incontra `N/A` in alcuni campi +- Quando si esegue `doublezero status` l'utente riscontra `N/A` in alcuni campi + + **Soluzioni:** @@ -293,14 +335,14 @@ Questo problema è generalmente correlato a una mancata corrispondenza tra il da up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta ``` - Nota nell'esempio sopra che il `Tunnel status` è `up`. La nostra `Network` è `mainnet-beta`. Tuttavia, `Current Device` e `Metro` sono `N/A`. - - Questo indica un tunnel aperto sulla tua macchina che non è nel tuo ambiente corrente. - In questo caso lo stato `up`, senza `Current Device` trovato su `mainnet-beta`, ci rivela che il nostro tunnel è su testnet! + Nota nel nostro output di esempio sopra che il `Tunnel status` è `up`. La nostra `Network` è `mainnet-beta`. Tuttavia, `Current Device` e `Metro` sono `N/A` + Questo è indicativo di un tunnel aperto sulla tua macchina che non si trova nel tuo ambiente corrente. + In questo caso lo stato `up`, senza nessun `Current Device` trovato su `mainnet-beta`, ci rivela che il nostro tunnel è su testnet! + 2. Cambia il tuo ambiente. - Per correggere la mancata corrispondenza cambierai il tuo ambiente all'opposto dell'ambiente che restituisce il `N/A` + Per correggere la discrepanza cambierai il tuo ambiente nell'opposto dell'ambiente che restituisce `N/A` ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -312,7 +354,7 @@ Questo problema è generalmente correlato a una mancata corrispondenza tra il da && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - Per configurare la CLI DoubleZero Client (`doublezero`) e il daemon (`doublezerod`) per connettersi al **mainnet-beta DoubleZero**: + Per configurare la CLI del Client DoubleZero (`doublezero`) e il daemon (`doublezerod`) per connettersi a **DoubleZero mainnet-beta**: ```bash DESIRED_DOUBLEZERO_ENV=mainnet-beta \ @@ -323,8 +365,8 @@ Questo problema è generalmente correlato a una mancata corrispondenza tra il da && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - -3. Controlla il tuo stato + +3. Verifica il tuo stato Dopo aver cambiato ambiente esegui: @@ -334,8 +376,8 @@ Questo problema è generalmente correlato a una mancata corrispondenza tra il da L'output atteso dovrebbe essere simile a: + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network - up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet - ``` -Con tutti i campi popolati sei ora nell'ambiente corretto. +Con tutti i campi popolati sei ora nell'ambiente corretto. \ No newline at end of file diff --git a/docs/troubleshooting.ja.md b/docs/troubleshooting.ja.md index 9b2091a..bb734c6 100644 --- a/docs/troubleshooting.ja.md +++ b/docs/troubleshooting.ja.md @@ -1,71 +1,77 @@ -# トラブルシューティング -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: DoubleZero の一般的な接続問題を診断するためのリファレンスコマンド、期待される出力、およびさらなるサポートの入手先について説明します。 +--- +# トラブルシューティング -このガイドではさまざまな問題を取り上げており、継続的に更新されています。ガイドを完了した後にさらなるサポートが必要な場合は、[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discordにてご支援を求めることができます。 +このガイドではさまざまな問題を取り上げており、随時更新されます。ガイドを最後まで確認しても問題が解決しない場合は、[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discord でさらなるサポートを受けることができます。 -## 共通コマンドと出力 +## よく使うコマンドと出力 -まず、以下のコマンドの出力と期待される出力を確認してください。これらは詳細なトラブルシューティングに役立ちます。 -サポートチケットを開いた場合、これらの出力を求められる場合があります。 +まず、以下のコマンドの出力と期待される出力を確認してください。これらはより詳細なトラブルシューティングに役立ちます。 +チケットを作成した場合、これらの出力を求められることがあります。 #### 1. バージョンの確認 コマンド: `doublezero --version` -サンプル出力: +出力例: ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) -#### 2. DoubleZeroアドレスの確認 +#### 2. DoubleZero アドレスの確認 コマンド: `doublezero address` -サンプル出力: +出力例: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () #### 3. アクセスパスの確認 -サンプル公開鍵:`MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` コマンド実行時はこれをあなたの公開鍵に置き換えてください。 +公開鍵の例:`MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` コマンド実行時にはご自身の公開鍵に置き換えてください。 コマンド: `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -出力:[この出力ではヘッダーを表示するために`doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'`を使用しています] +出力:[この出力でヘッダーを表示するために `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` を使用しています] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. DoubleZeroレジャークレジットの確認 +[comment]: # () +#### 4. DoubleZero 台帳クレジットの確認 コマンド: `doublezero balance` -サンプル出力: +出力例: ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) -#### 5. 接続ステータスの確認 +#### 5. 接続状態の確認 コマンド: `doublezero status` -サンプル出力: +出力例: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) #### 6. レイテンシの確認 @@ -73,9 +79,9 @@ account | accesspass_type `doublezero latency` -サンプル出力: +出力例: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -86,72 +92,72 @@ account | accesspass_type 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () # トラブルシューティングの例 -基本的な出力と正常なデプロイメントで期待される内容を確認したところで、一般的なトラブルシューティングの例を見ていきましょう。 +基本的な出力と正常なデプロイメントで期待される内容を確認したので、一般的なトラブルシューティングの例を見ていきましょう。 ### 問題:❌ Error creating user -この問題は一般的に、期待される公開鍵/IPのペアリングとユーザーがDoubleZeroへのアクセスを試みている公開鍵/IPのペアリングの不一致に関連しています。 +この問題は一般的に、期待される公開鍵/IPの組み合わせと、ユーザーが DoubleZero にアクセスしようとしている際の公開鍵/IPの組み合わせが一致しないことに関連しています。 **症状:** -- `doublezero connect ibrl`で接続する際に`❌ Error creating user`が表示される +- `doublezero connect ibrl` で接続しようとすると `❌ Error creating user` が発生する -**解決策:** +**解決方法:** 1. 確認 `doublezero address` - サンプル出力: + 出力例: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` -2. このアドレスが許可リストに登録されているか確認します: +2. このアドレスが許可リストに登録されていることを確認: `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` - サンプル出力: + 出力例: ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - `doublezero address`の公開鍵はuser_payerの公開鍵と一致し、接続しようとしているIPアドレスはアクセスパスのIPと一致する必要があります。 - `doublezero address`はデフォルトで`~/.config/doublezero/`のid.jsonファイルから取得されます。[ステップ6](https://docs.malbeclabs.com/setup/)を参照してください。 - -3. 上記が正しく、接続中にエラーが発生している場合、またはマッピングが間違っている場合は、[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701)でサポートにお問い合わせください。 + `doublezero address` の公開鍵は user_payer の公開鍵と一致する必要があり、接続元の IP アドレスはアクセスパスの ip と一致する必要があります。 + `doublezero address` はデフォルトで ~/.config/doublezero/ にある id.json ファイルから取得されます。[こちらのステップ6]()を参照してください。 + +3. 上記が正しいにもかかわらず接続時にエラーが発生する場合、または上記のマッピングが正しくない場合は、[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) でサポートにお問い合わせください。 ### 問題:❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time -このエラーは、デバイスがすでにDoubleZeroに接続されていることを示します。 +このエラーは、デバイスが既に DoubleZero に接続されていることを意味します。 **症状:** -- ユーザーがDoubleZeroへの接続を試みる -- `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`が表示される +- ユーザーが DoubleZero に接続しようとする +- `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time` が発生する -**解決策:** +**解決方法:** 1. 確認 `doublezero status` 出力: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `up`は正常な接続を示します。 -3. このエラーは、特定のDoubleZero IPを持つDoubleZeroへのトンネルがこのマシンですでにアクティブであるために表示されます。 - - このエラーはDoubleZeroクライアントのアップグレード後によく発生します。DoubleZeroのアップグレードはdoublezerodサービスを自動的に再起動し、サービス再起動前に接続していた場合は再接続されます。 +2. -`up`- は正常な接続を示します。 +3. このエラーは、特定の DoubleZero IP を使用した DoubleZero へのトンネルがこのマシン上で既にアクティブであるために表示されます。 + このエラーは DoubleZero クライアントのアップグレード後によく発生します。DoubleZero のアップグレードは doublezerod サービスを自動的に再起動し、サービス再起動前に接続していた場合は再接続します。 -### 問題:DoubleZeroのステータスがunknownまたはdown -この問題は、サーバーとDoubleZeroデバイス間でGREトンネルが正常にアクティブ化されたが、ファイアウォールがBGPセッションの確立を妨げていることに関連することが多いです。そのため、ネットワークからルートを受信せず、DoubleZero上でトラフィックを送信していません。 +### 問題:DoubleZero のステータスが unknown または down +この問題は、サーバーと DoubleZero デバイス間で GRE トンネルは正常に確立されたものの、ファイアウォールが BGP セッションの確立を妨げていることに関連していることが多いです。このため、ネットワークからルートを受信できず、DoubleZero 経由でトラフィックを送信できません。 **症状:** -- `doublezero connect ibrl`は成功しました。ただし、`doublezero status`が`down`または`unknown`を返す +- `doublezero connect ibrl` は成功した。しかし、`doublezero status` が `down` または `unknown` を返す ``` - doublezero connect ibrl + doublezero connect ibrl DoubleZero Service Provisioning 🔗 Start Provisioning User... Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` @@ -164,18 +170,18 @@ account | accesspass_type ``` ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -**解決策:** +**解決方法:** 1. ファイアウォールルールを確認してください! - DoubleZeroはGREトンネルインターフェース用にリンクローカルアドレス空間169.254.0.0/16をマシンとDoubleZeroデバイス間で使用します。169.254.0.0/16は通常「非ルーティング可能」な空間であり、優れたセキュリティプラクティスではこの空間との通信をブロックすることが推奨されます。169.254.0.0/16を送信元とする通信がtcpポート179の169.254.0.0/16宛先と通信できるファイアウォールルールを許可する必要があります。そのルールは169.254.0.0/16へのトラフィックを拒否するルールより上に配置する必要があります。 + DoubleZero はマシンと DoubleZero デバイス間の GRE トンネルインターフェースにリンクローカルアドレス空間 169.254.0.0/16 を使用します。169.254.0.0/16 は通常「ルーティング不可」な空間であり、適切なセキュリティプラクティスではこの空間との通信をブロックすることが推奨されます。ファイアウォールで src 169.254.0.0/16 から dst 169.254.0.0/16 への tcp ポート 179 での通信を許可するルールを追加する必要があります。このルールは 169.254.0.0/16 へのトラフィックを拒否するルールよりも上に配置する必要があります。 - UFWなどのファイアウォールでは`sudo ufw status`を実行してファイアウォールルールを確認できます。 + ufw のようなファイアウォールでは `sudo ufw status` を実行してファイアウォールルールを確認できます。 - Solanaバリデーターが持つような設定に類似したサンプル出力: + Solana バリデーターが使用するような設定の出力例: ``` To Action From -- ------ ---- @@ -194,83 +200,83 @@ account | accesspass_type 192.168.0.0/16 DENY OUT Anywhere ``` - 上記の出力では、指定されたポート以外の169.254.0.0/16へのすべてのトラフィックが拒否されています。 - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179`でルールを番目の位置に挿入します。例:N=1の場合、このルールを最初のルールとして挿入します。 - `sudo ufw status numbered`でルールの番号順序を確認できます。 + 上記の出力では、指定されたポートを除く 169.254.0.0/16 へのすべてのトラフィックが拒否されています。 + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` でルールを の位置に挿入します。例えば N = 1 の場合、このルールは最初のルールとして挿入されます。 + `sudo ufw status numbered` でルールの番号順を確認できます。 + +### 問題:最寄りの DoubleZero デバイスが変更された -### 問題:最寄りのDoubleZeroデバイスが変わった +これはエラーではありませんが、最適化の機会です。以下は、時折実行するか自動化できるベストプラクティスです。 -これはエラーではありませんが、最適化できる場合があります。以下は時折実行、または自動化できるベストプラクティスです。 +**解決方法:** -**解決策:** - -1. 最寄りのデバイスへのレイテンシを確認する - - `doublezero latency`を実行 +1. 最寄りのデバイスへのレイテンシを確認 + - `doublezero latency` を実行 出力 ``` - pubkey | code | ip | min | max | avg | reachable - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true - ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true - 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true - 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true - FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true ``` - 上記では最寄りのデバイスが`dz-ny7-sw01`です + 上記で最寄りのデバイスは `dz-ny7-sw01` です - このデバイスに接続したいとします。 + このデバイスに接続します: -2. 対象デバイスに既に接続されているか確認する - - `doublezero user list --env testnet | grep 111.11.11.11`を実行。`111.11.11.11`をDoubleZeroに接続されているデバイスの公開IPv4アドレスに置き換えてください。バリデーターIDまたはDoubleZero IDも使用できます。 +2. 対象デバイスに既に接続しているか確認 + - `doublezero user list --env testnet | grep 111.11.11.11` を実行します。`111.11.11.11` を DoubleZero に接続しているデバイスのパブリック IPv4 アドレスに置き換えてください。バリデーター ID や DoubleZero ID を使用することもできます。 出力 ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - この例では、既に最寄りのデバイスに接続されています。これ以上のステップは不要で、ここで止めることができます。 + この例では、既に最寄りのデバイスに接続されています。これ以上の手順は不要です。ここで終了できます。 - 代わりに出力が次のようだった場合を考えてみましょう: + 代わりに以下のような出力だった場合を考えてみましょう ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` これは最適でない接続です。再接続が必要かどうか検討しましょう。 - 接続の前に、デバイスに利用可能なユーザートンネルがあるか確認します。 + 接続する前に、デバイスに利用可能なユーザートンネルがあるか確認します。 -3. オプション:利用可能なデバイスのネットワークを調べる +3. オプション:ネットワーク上の利用可能なデバイスを確認 - 教育目的で、最初に: - - `doublezero device list`を実行して全デバイスリストを取得します。出力を説明するために2台のデバイスを例として示します。 + 学習目的で、まず以下を実行します: + - `doublezero device list` でデバイスの完全なリストを表示します。出力を説明するために2つのデバイスを例として抜粋しています。 出力: ``` - account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner - GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp - 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - `ams001-dz002`は69ユーザーいて、最大128ユーザーまで対応できます。このデバイスは59人のユーザーを追加できます。 + 上記で `ams001-dz002` は 69 ユーザーで、最大 128 ユーザーです。このデバイスはあと 59 ユーザーを追加できます。 - 一方、`dz-fr5-sw01`は0ユーザーで最大0ユーザーです。このデバイスには接続できません。最大ユーザーが0の場合、デバイスは接続を受け付けていません。 + しかし、`dz-fr5-sw01` は 0 ユーザーで、最大ユーザー数も 0 です。このデバイスには接続できません。最大ユーザー数が 0 のため、デバイスは接続を受け付けていません。 - 最寄りのデバイスへの接続に戻りましょう。 + では、最寄りのデバイスへの接続に戻りましょう。 -4. 対象デバイスに利用可能な接続があるか確認する - - `doublezero device list | grep dz-ny7-sw01`を実行。`dz-ny7-sw01`を対象デバイスに置き換えてください +4. 対象デバイスに利用可能な接続があるか確認 + - `doublezero device list | grep dz-ny7-sw01` を実行します。`dz-ny7-sw01` を対象デバイスに置き換えてください。 出力 ``` - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - `dz-ny7-sw01`に接続可能なスペースがあることがわかります。 + ここで `dz-ny7-sw01` に接続可能な空きがあることが確認できます。 -5. 最寄りのDoubleZeroデバイスに接続する +5. 最寄りの DoubleZero デバイスに接続 - 切断してからDoubleZeroに再接続します。 + 切断してから DoubleZero に再接続します。 - まず実行: + まず以下を実行 - `doublezero disconnect` 出力 @@ -283,16 +289,16 @@ account | accesspass_type 🔍 User Account deleted ✅ Deprovisioning Complete ``` - 次に切断確認のためステータスを確認します: + 次にステータスを確認して切断を確認します - `doublezero status` 出力 ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type - disconnected | no session data | | | | | + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | ``` - 最後に再接続します: + 最後に再接続します - `doublezero connect ibrl` 出力 @@ -302,23 +308,23 @@ account | accesspass_type Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` 🔍 Provisioning User for IP: 111.11.11.11 User account created - Connected to device: dz-ny7-sw01 + Connected to device: dz-ny7-sw01 Service provisioned with status: ok ✅ User Provisioned ``` - 上記の出力で`Connected to device: dz-ny7-sw01`が表示されています。これはステップ1の最初の調査で`dz-ny7-sw01`が最も低いレイテンシのデバイスであると判明したことから、望ましい結果です。 + 上記の出力で `Connected to device: dz-ny7-sw01` に接続したことに注目してください。これはステップ1の初期調査で `dz-ny7-sw01` が最も低レイテンシのデバイスであると判明した結果から期待される結果です。 -### 問題:`doublezero status`で一部のフィールドにN/Aが表示される +### 問題:`doublezero status` の一部のフィールドが N/A を返す -この問題は一般的に、現在のデーモンとクライアントと、接続されたDZトンネルが確立されたデーモンとクライアントの不一致に関連しています。 +この問題は一般的に、現在のデーモンとクライアントと、接続中の DZ トンネルが確立された時点のデーモンとクライアントとの間の不一致に関連しています。 **症状:** -- `doublezero status`を実行すると一部のフィールドに`N/A`が表示される +- `doublezero status` を実行すると、一部のフィールドに `N/A` が表示される -**解決策:** +**解決方法:** 1. 実行 `doublezero status` @@ -329,14 +335,14 @@ account | accesspass_type up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta ``` - 上記の出力例で、`Tunnel status`が`up`であることに注意してください。`Network`は`mainnet-beta`ですが、`Current Device`と`Metro`は`N/A`です。 - - これは、マシン上のオープントンネルが現在の環境にないことを示しています。 - この場合、`mainnet-beta`で`Current Device`が見つからない`up`ステータスは、トンネルがテストネット上にあることを示しています! + 上記の出力例で、`Tunnel status` が `up` であることに注目してください。`Network` は `mainnet-beta` です。しかし、`Current Device` と `Metro` が `N/A` になっています。 + これは、現在の環境にないオープンなトンネルがマシン上に存在することを示しています。 + この場合、`up` のステータスと、`mainnet-beta` 上で `Current Device` が見つからないことから、トンネルが testnet 上にあることがわかります! + 2. 環境を変更する - 不一致を解消するには、`N/A`を返す環境の反対の環境に変更します。 + 不一致を修正するには、`N/A` を返している環境とは反対の環境に変更します。 ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -348,7 +354,7 @@ account | accesspass_type && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - DoubleZeroクライアントCLI(`doublezero`)とデーモン(`doublezerod`)を**DoubleZeroメインネットベータ**に接続するように設定するには: + DoubleZero クライアント CLI(`doublezero`)とデーモン(`doublezerod`)を **DoubleZero mainnet-beta** に接続するよう設定するには: ```bash DESIRED_DOUBLEZERO_ENV=mainnet-beta \ @@ -359,19 +365,17 @@ account | accesspass_type && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` + +3. ステータスを確認 -3. ステータスを確認する - - 環境の切り替え後に実行: + 環境を切り替えた後、以下を実行します: ``` doublezero status ``` - 期待される出力は次のようになります: + 期待される出力は以下のようになるはずです: - ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network - up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet - ``` -すべてのフィールドが入力されており、正しい環境にいることを示しています。 + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | \ No newline at end of file diff --git a/docs/troubleshooting.ko.md b/docs/troubleshooting.ko.md index 49a052e..14ce29c 100644 --- a/docs/troubleshooting.ko.md +++ b/docs/troubleshooting.ko.md @@ -1,80 +1,87 @@ -# 문제 해결 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 일반적인 DoubleZero 연결 문제를 참조 명령어, 예상 출력, 추가 지원을 받을 수 있는 곳과 함께 진단합니다. +--- +# 문제 해결 -이 가이드는 다양한 문제를 다루며 계속 업데이트됩니다. 가이드를 완료한 후에도 추가 지원이 필요하다면 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discord에서 도움을 받을 수 있습니다. +이 가이드는 다양한 문제를 다루며, 지속적으로 업데이트됩니다. 가이드를 완료한 후에도 추가 지원이 필요하면 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discord에서 도움을 받을 수 있습니다. -## 일반 명령 및 출력 +## 일반적인 명령어 및 출력 -다음 명령의 출력과 정상 배포 시 예상되는 출력을 살펴보겠습니다. 이는 더 자세한 문제 해결에 도움이 될 것입니다. -티켓을 제출하면 출력을 요청받을 수 있습니다. +먼저 다음 명령어의 출력과 예상 출력을 확인하세요. 이 정보는 보다 자세한 문제 해결에 도움이 됩니다. +티켓을 여는 경우 해당 출력을 요청받을 수 있습니다. #### 1. 버전 확인 -명령: +명령어: `doublezero --version` -샘플 출력: +예시 출력: ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) #### 2. DoubleZero 주소 확인 -명령: +명령어: `doublezero address` -샘플 출력: +예시 출력: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () -#### 3. 액세스 패스 확인 +#### 3. Access Pass 확인 -샘플 공개 키: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` — 명령 실행 시 이것을 본인의 공개 키로 교체하세요. +예시 공개키: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` 명령어를 실행할 때 이것을 본인의 공개키로 대체하세요. -명령: +명령어: `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -출력: +출력: [참고로 이 출력에서 헤더를 표시하기 위해 `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'`를 사용합니다] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. DoubleZero 레저 크레딧 확인 -명령: +[comment]: # () +#### 4. DoubleZero 원장 크레딧 확인 +명령어: `doublezero balance` -샘플 출력: +예시 출력: ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) #### 5. 연결 상태 확인 -명령: +명령어: `doublezero status` -샘플 출력: +예시 출력: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) + -#### 6. 대기 시간 확인 -명령: +#### 6. 지연 시간 확인 +명령어: `doublezero latency` -샘플 출력: +예시 출력: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -85,48 +92,49 @@ account | accesspass_type 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () -# 문제 해결 예제 -기본 출력과 정상 배포 시 예상되는 것을 살펴봤으니 이제 일반적인 문제 해결 예제를 살펴보겠습니다. +# 문제 해결 예시 +이제 기본 출력과 정상적인 배포에서 예상되는 내용을 살펴보았으므로 몇 가지 일반적인 문제 해결 예시를 살펴보겠습니다. -### 문제: ❌ 사용자 생성 오류 +### 문제: ❌ Error creating user -이 문제는 일반적으로 예상되는 공개 키/IP 쌍과 사용자가 DoubleZero에 접근하려는 공개 키/IP 쌍 간의 불일치와 관련이 있습니다. +이 문제는 일반적으로 예상되는 공개키/IP 쌍과 사용자가 DoubleZero에 접속하려는 공개키/IP 쌍이 일치하지 않는 것과 관련이 있습니다. **증상:** -- `doublezero connect ibrl`로 연결할 때 `❌ Error creating user`가 발생합니다 +- `doublezero connect ibrl`로 연결할 때 사용자가 `❌ Error creating user`를 만남 **해결 방법:** -1. 확인 +1. 확인 `doublezero address` - 샘플 출력: + 예시 출력: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` -2. 이 주소가 허용 목록에 있는지 확인: +2. 이 주소가 허용 목록에 있는지 확인: `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` - 샘플 출력: + 예시 출력: ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - `doublezero address`의 공개 키는 user_payer 공개 키와 일치해야 하며, 연결하려는 IP 주소는 액세스 패스의 IP와 일치해야 합니다. - `doublezero address`는 기본적으로 `~/.config/doublezero/`의 id.json 파일에서 가져옵니다. [여기 6단계](https://docs.malbeclabs.com/setup/)를 참조하세요. - -3. 위 내용이 올바르게 보이지만 연결 중 오류가 발생하거나 위 매핑이 올바르지 않은 경우 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701)에서 지원에 연락하세요. + `doublezero address`의 공개키는 user_payer 공개키와 일치해야 하며, 연결하려는 IP 주소는 Access-Pass의 ip와 일치해야 합니다. + `doublezero address`는 기본적으로 ~/.config/doublezero/에 있는 id.json 파일에서 가져옵니다. [여기 6단계]()를 참조하세요. + +3. 위 내용이 올바른데도 연결 시 오류가 발생하거나, 위 매핑이 잘못된 경우 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701)에서 지원팀에 문의하세요. -### 문제: ❌ 서비스 프로비저닝 오류: 동시에 여러 터널을 프로비저닝할 수 없습니다 +### 문제: ❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time 이 오류는 장치가 이미 DoubleZero에 연결되어 있음을 나타냅니다. **증상:** -- 사용자가 DoubleZero에 연결하려고 합니다 -- `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`이 발생합니다 +- 사용자가 DoubleZero에 연결하려고 시도 +- `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`가 발생 **해결 방법:** 1. 확인 @@ -134,29 +142,46 @@ account | accesspass_type 출력: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `up`은 정상적인 연결을 나타냅니다. -3. 오류는 특정 DoubleZero IP를 사용하는 DoubleZero 터널이 이미 이 기계에서 활성화되어 있기 때문에 나타납니다. +2. -`up`-은 정상적인 연결을 나타냅니다. +3. 이 오류는 특정 DoubleZero IP를 사용하는 DoubleZero 터널이 이 머신에서 이미 활성화되어 있기 때문에 나타납니다. - 이 오류는 DoubleZero 클라이언트 업그레이드 후 자주 발생합니다. DoubleZero 업그레이드는 doublezerod 서비스를 자동으로 재시작하고 서비스 재시작 전에 연결되어 있었다면 다시 연결합니다. + 이 오류는 DoubleZero 클라이언트 업그레이드 후에 자주 발생합니다. DoubleZero 업그레이드는 자동으로 doublezerod 서비스를 재시작하고, 서비스 재시작 전에 연결되어 있었다면 다시 연결합니다. ### 문제: DoubleZero 상태가 unknown 또는 down -이 문제는 종종 서버와 DoubleZero 장치 간의 GRE 터널이 성공적으로 활성화되었지만 방화벽이 BGP 세션 설정을 방지하는 것과 관련이 있습니다. 이로 인해 네트워크로부터 경로를 받지 못하거나 DoubleZero를 통해 트래픽을 전송하지 못합니다. +이 문제는 서버와 DoubleZero 장치 간 GRE 터널이 성공적으로 활성화되었지만, 방화벽이 BGP 세션 설정을 차단하는 경우와 관련이 있습니다. 이로 인해 네트워크에서 경로를 수신하지 못하거나 DoubleZero를 통해 트래픽을 전송하지 못합니다. **증상:** -- `doublezero connect ibrl`이 성공했습니다. 그러나 `doublezero status`가 `down` 또는 `unknown`을 반환합니다 +- `doublezero connect ibrl`은 성공했지만, `doublezero status`가 `down` 또는 `unknown`을 반환 + ``` + doublezero connect ibrl + DoubleZero Service Provisioning + 🔗 Start Provisioning User... + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + 🔍 Provisioning User for IP: 111.11.11.11 + User account created + Connected to device: nyc-dz001 + The user has been successfully activated + Service provisioned with status: ok + ✅ User Provisioned + ``` + + ```bash + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet + ``` **해결 방법:** 1. 방화벽 규칙을 확인하세요! - DoubleZero는 귀하의 기계와 DoubleZero 장치 간의 GRE 터널 인터페이스에 링크 로컬 주소 공간인 169.254.0.0/16을 사용합니다. 169.254.0.0/16은 일반적으로 "라우팅 불가능한" 공간으로 간주되므로 좋은 보안 관행은 이 공간에서 오가는 통신을 차단하는 것을 권장합니다. 방화벽에서 src 169.254.0.0/16이 tcp 포트 179의 dst 169.254.0.0/16과 통신할 수 있도록 하는 규칙을 허용해야 합니다. 해당 규칙은 169.254.0.0/16 트래픽을 거부하는 규칙 위에 배치되어야 합니다. + DoubleZero는 머신과 DoubleZero 장치 간 GRE 터널 인터페이스에 링크 로컬 주소 공간 169.254.0.0/16을 사용합니다. 169.254.0.0/16은 일반적으로 "라우팅 불가능한" 주소 공간이며, 따라서 올바른 보안 관행에서는 이 공간으로의 통신을 차단할 것을 권장합니다. 방화벽에서 출발지 169.254.0.0/16이 목적지 169.254.0.0/16과 TCP 포트 179로 통신할 수 있도록 허용하는 규칙을 추가해야 합니다. 이 규칙은 169.254.0.0/16 트래픽을 거부하는 규칙보다 상위에 배치해야 합니다. - ufw와 같은 방화벽에서 `sudo ufw status`를 실행하여 방화벽 규칙을 볼 수 있습니다. + ufw와 같은 방화벽에서는 `sudo ufw status`를 실행하여 방화벽 규칙을 확인할 수 있습니다. - Solana 검증자와 유사할 수 있는 샘플 출력: + Solana 검증자가 사용할 수 있는 것과 유사한 예시 출력입니다. ``` To Action From -- ------ ---- @@ -175,53 +200,105 @@ account | accesspass_type 192.168.0.0/16 DENY OUT Anywhere ``` - 위 출력에서 지정된 포트를 제외한 169.254.0.0/16으로의 모든 트래픽이 거부됩니다. - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179`를 사용하여 \ 위치에 규칙을 삽입하세요. 즉, N = 1이면 이 규칙을 첫 번째 규칙으로 삽입합니다. - `sudo ufw status numbered`는 규칙의 번호 순서를 표시합니다. - + 위 출력에서 지정된 포트를 제외한 169.254.0.0/16으로의 모든 트래픽이 거부되는 것을 볼 수 있습니다. + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179`를 실행하여 위치에 규칙을 삽입합니다. 예: N = 1이면 이 규칙을 첫 번째 규칙으로 삽입합니다. + `sudo ufw status numbered`를 실행하면 규칙의 숫자 순서를 확인할 수 있습니다. + ### 문제: 가장 가까운 DoubleZero 장치가 변경됨 -이것은 오류가 아니라 최적화일 수 있습니다. 아래는 때때로 실행하거나 자동화할 수 있는 모범 사례입니다. +이것은 오류가 아니라 최적화가 될 수 있습니다. 아래는 수시로 실행하거나 자동화할 수 있는 모범 사례입니다. **해결 방법:** -1. 가장 가까운 장치의 대기 시간 확인 +1. 가장 가까운 장치까지의 지연 시간 확인 - `doublezero latency` 실행 출력 ``` - pubkey | code | ip | min | max | avg | reachable - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true - ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true - 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true - 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true - FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true ``` 위에서 가장 가까운 장치는 `dz-ny7-sw01`입니다. - 이 장치에 연결하고자 합니다. + 이 장치에 연결하고자 합니다: 2. 이미 대상 장치에 연결되어 있는지 확인 - - `doublezero user list --env testnet | grep 111.11.11.11`을 실행하여 `111.11.11.11`을 DoubleZero에 연결된 장치의 공개 IPv4 주소로 교체하세요. + - `doublezero user list --env testnet | grep 111.11.11.11` 실행. `111.11.11.11`을 DoubleZero에 연결된 장치의 공용 IPv4 주소로 대체하세요. 검증자 ID 또는 doublezero ID를 사용할 수도 있습니다. 출력 ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + 이 예시에서는 이미 가장 가까운 장치에 연결되어 있습니다. 추가 단계가 필요하지 않으며 여기서 중단할 수 있습니다. + + + 대신 출력이 다음과 같다고 가정해 봅시다 + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + 이것은 최적이 아닌 연결입니다. 재연결이 필요한지 살펴보겠습니다. + + 연결하기 전에 장치에 사용 가능한 사용자 터널이 있는지 확인합니다. + +3. 선택 사항: 사용 가능한 장치에 대해 네트워크 조사 + + 학습 목적으로 먼저: + - `doublezero device list`를 실행하여 전체 장치 목록을 확인합니다. 출력을 설명하기 위해 2개의 장치를 예시로 가져왔습니다. + + 출력: + ``` + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - 이 예에서 우리는 이미 가장 가까운 장치에 연결되어 있습니다. 더 이상의 단계가 필요 없으며 여기서 멈출 수 있습니다. + 위에서 `ams001-dz002`는 69명의 사용자와 128명의 최대 사용자가 있습니다. 이 장치는 59명의 사용자를 추가할 수 있습니다. + + 그러나 `dz-fr5-sw01`은 0명의 사용자와 0명의 최대 사용자가 있습니다. 이 장치에는 연결할 수 없습니다. 최대 사용자가 0이므로 장치가 어떠한 연결도 수락하지 않습니다. + + 이제 가장 가까운 장치에 연결하는 것으로 돌아가겠습니다. + +4. 대상 장치에 사용 가능한 연결이 있는지 확인 + - `doublezero device list | grep dz-ny7-sw01` 실행. `dz-ny7-sw01`을 대상 장치로 대체하세요. + + 출력 + ``` + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + ``` + 여기서 `dz-ny7-sw01`에 연결 가능한 공간이 있음을 확인할 수 있습니다. 5. 가장 가까운 DoubleZero 장치에 연결 - 연결을 끊고 DoubleZero에 다시 연결합니다. + 연결을 해제한 다음 DoubleZero에 재연결합니다. - 먼저 실행: + 먼저 실행 - `doublezero disconnect` - 이제 상태를 확인하여 연결 해제를 확인합니다: + 출력 + + ``` + DoubleZero Service Provisioning + 🔍 Decommissioning User + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + \ [00:00:00] [##########>-----------------------------] 1/4 deleting user account... 🔍 Deleting User Account for: 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW + 🔍 User Account deleted + ✅ Deprovisioning Complete + ``` + 이제 다음 명령으로 연결 해제를 확인합니다. - `doublezero status` - 마지막으로 다시 연결: + 출력 + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | + ``` + 마지막으로 다음 명령으로 재연결합니다. - `doublezero connect ibrl` 출력 @@ -231,61 +308,42 @@ account | accesspass_type Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` 🔍 Provisioning User for IP: 111.11.11.11 User account created - Connected to device: dz-ny7-sw01 + Connected to device: dz-ny7-sw01 Service provisioned with status: ok ✅ User Provisioned ``` - 위 출력에서 `Connected to device: dz-ny7-sw01`로 연결되었음을 확인하세요. + 위 출력에서 `Connected to device: dz-ny7-sw01`에 연결된 것을 확인할 수 있습니다. 이것은 1단계의 초기 조사에서 `dz-ny7-sw01`이 가장 낮은 지연 시간을 가진 장치임을 발견한 것에 따른 원하는 결과입니다. -### 문제: `doublezero status`가 일부 필드에서 N/A를 반환함 +### 문제: `doublezero status`에서 일부 필드가 N/A를 반환 이 문제는 일반적으로 현재 데몬 및 클라이언트와 연결된 DZ 터널이 설정된 데몬 및 클라이언트 간의 불일치와 관련이 있습니다. **증상:** -- `doublezero status`를 실행할 때 일부 필드에서 `N/A`가 발생합니다 +- `doublezero status` 실행 시 일부 필드에서 `N/A`가 나타남 + + + **해결 방법:** 1. 실행 `doublezero status` - 예: + 예시: ``` Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta ``` - 위 예제 출력에서 `Tunnel status`가 `up`임을 확인하세요. `Network`는 `mainnet-beta`입니다. 그러나 `Current Device`와 `Metro`가 `N/A`입니다. - - 이것은 현재 환경에 없는 열린 터널이 기계에 있음을 나타냅니다. - 이 경우 `mainnet-beta`에서 `Current Device`가 없는 `up` 상태는 터널이 testnet에 있음을 나타냅니다! + 위 예시 출력에서 `Tunnel status`가 `up`인 것을 확인할 수 있습니다. `Network`는 `mainnet-beta`입니다. 그러나 `Current Device`와 `Metro`가 `N/A`입니다. -2. 환경을 변경하세요. + 이것은 현재 환경에 속하지 않는 열린 터널이 머신에 있음을 나타냅니다. + 이 경우 `up` 상태에서 `mainnet-beta`에서 `Current Device`를 찾지 못한 것은 터널이 testnet에 있다는 것을 알려줍니다! + +2. 환경을 변경합니다. - 불일치를 수정하려면 환경을 `N/A`를 반환하는 환경의 반대로 변경합니다. + 불일치를 해결하기 위해 `N/A`를 반환하는 환경의 반대 환경으로 변경합니다. ```bash DESIRED_DOUBLEZERO_ENV=testnet \ - && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \ - && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \ - && sudo systemctl daemon-reload \ - && sudo systemctl restart doublezerod \ - && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ - && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" - ``` - -3. 상태 확인 - - 환경 전환 후 실행: - - ``` - doublezero status - ``` - - 예상 출력은 다음과 유사해야 합니다: - - ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network - up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet - ``` -모든 필드가 채워지면 올바른 환경에 있는 것입니다. + && sudo mkdir -p /etc/systemd/system/doublezerod. \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 313846b..e01c7e6 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -125,7 +125,7 @@ This issue is generally related to a mismatch between the expected pubkey/IP pai FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` The pubkey from `doublezero address` must match the user_payer pubkey and the IP Address you are trying to connect from must match the ip in the Access-Pass. - `doublezero address` is sourced from the id.json file in in ~/.config/doublezero/ by default. See the [step 6 here](https://docs.malbeclabs.com/setup/) + `doublezero address` is sourced from the id.json file in in ~/.config/doublezero/ by default. See the [step 6 here]() 3. If the above looks correct and you are getting an error while connecting or if the above mapping is incorrect please contact support in [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) diff --git a/docs/troubleshooting.pt.md b/docs/troubleshooting.pt.md index 608ef58..14dde1b 100644 --- a/docs/troubleshooting.pt.md +++ b/docs/troubleshooting.pt.md @@ -1,71 +1,77 @@ -# Solução de Problemas -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: Diagnostique problemas comuns de conexão DoubleZero com comandos de referência, saídas esperadas e onde obter suporte adicional. +--- +# Resolução de Problemas -Este guia cobrirá uma variedade de problemas, e está em andamento. Se você completar o guia, pode buscar suporte adicional no Discord [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). +Este guia cobrirá uma variedade de problemas e está em constante atualização. Se você concluir o guia e ainda precisar de ajuda, pode buscar suporte adicional no discord [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701). ## Comandos Comuns e Saídas -Para começar, examine a saída dos seguintes comandos e sua saída esperada. Eles irão auxiliá-lo em uma solução de problemas mais detalhada. -Se você abrir um ticket, pode ser solicitado que você forneça a saída deles. +Para começar, examine a saída dos seguintes comandos e suas saídas esperadas. Eles irão auxiliá-lo em uma resolução de problemas mais detalhada. +Se você abrir um ticket, poderá ser solicitado a fornecer essas saídas. #### 1. Verificar Versão Comando: `doublezero --version` -Exemplo de Saída: +Saída de Exemplo: ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) #### 2. Verificar Endereço DoubleZero Comando: `doublezero address` -Exemplo de Saída: +Saída de Exemplo: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () #### 3. Verificar seu Passe de Acesso -Pubkey de exemplo: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` — substitua pela sua pubkey ao executar o comando. +Pubkey de exemplo: `MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` substitua isso pela sua pubkey ao executar o comando. Comando: `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -Saída: +Saída: [note que usamos `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` para mostrar o cabeçalho nesta saída] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. Verificar Créditos do Ledger DoubleZero +[comment]: # () +#### 4. Verificar Créditos no Ledger DoubleZero Comando: `doublezero balance` -Exemplo de Saída: +Saída de Exemplo: ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) #### 5. Verificar Status da Conexão Comando: `doublezero status` -Exemplo de Saída: +Saída de Exemplo: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) #### 6. Verificar Latência @@ -73,9 +79,9 @@ Comando: `doublezero latency` -Exemplo de Saída: +Saída de Exemplo: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -86,16 +92,17 @@ Exemplo de Saída: 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () -# Exemplos de Solução de Problemas -Agora que examinamos as saídas básicas e o que é esperado em uma implantação saudável, podemos examinar alguns exemplos comuns de solução de problemas. +# Exemplos de Resolução de Problemas +Agora que examinamos as saídas básicas e o que é esperado em uma implantação saudável, podemos examinar alguns exemplos comuns de resolução de problemas. ### Problema: ❌ Error creating user Este problema geralmente está relacionado a uma incompatibilidade entre o par pubkey/IP esperado e o par pubkey/IP que o usuário está tentando usar para acessar o DoubleZero. **Sintomas:** -- Ao conectar com `doublezero connect ibrl`, o usuário encontra `❌ Error creating user` +- Ao conectar com `doublezero connect ibrl` o usuário encontra `❌ Error creating user` **Soluções:** @@ -103,30 +110,30 @@ Este problema geralmente está relacionado a uma incompatibilidade entre o par p `doublezero address` - Exemplo de Saída: + Saída de Exemplo: ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` -2. Verifique se este endereço está na lista de permitidos: +2. verifique se este endereço está na lista de permitidos: `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` - Exemplo de Saída: + Saída de Exemplo: ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - A pubkey de `doublezero address` deve corresponder à pubkey do user_payer e o endereço IP do qual você está tentando se conectar deve corresponder ao IP no Passe de Acesso. - `doublezero address` é obtido do arquivo id.json em ~/.config/doublezero/ por padrão. Consulte o [passo 6 aqui](https://docs.malbeclabs.com/setup/) - -3. Se o acima parece correto e você está recebendo um erro ao conectar ou se o mapeamento acima estiver incorreto, entre em contato com o suporte em [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) + A pubkey de `doublezero address` deve corresponder à pubkey user_payer e o Endereço IP a partir do qual você está tentando conectar deve corresponder ao ip no Passe de Acesso. + `doublezero address` é obtido do arquivo id.json em ~/.config/doublezero/ por padrão. Veja o [passo 6 aqui]() + +3. Se o acima parecer correto e você estiver recebendo um erro ao conectar, ou se o mapeamento acima estiver incorreto, entre em contato com o suporte em [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) ### Problema: ❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time -Este erro indica que um dispositivo já está conectado ao DoubleZero. +Este erro significa que um dispositivo já está conectado ao DoubleZero. **Sintomas:** -- O usuário tenta se conectar ao DoubleZero +- O usuário tenta conectar ao DoubleZero - `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time` é encontrado. **Soluções:** @@ -135,69 +142,207 @@ Este erro indica que um dispositivo já está conectado ao DoubleZero. Saída: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `up` indica uma conexão saudável. +2. -`up`- indica uma conexão saudável. 3. O erro aparece porque um túnel para o DoubleZero com o IP DoubleZero específico já está ativo nesta máquina. - Este erro é frequentemente encontrado após uma atualização do cliente DoubleZero. As atualizações do DoubleZero reiniciam automaticamente o serviço doublezerod e reconectarão você se você estivesse conectado antes da reinicialização do serviço. + Este erro é frequentemente encontrado após uma atualização do cliente DoubleZero. As atualizações do DoubleZero reiniciam automaticamente o serviço doublezerod e irão reconectá-lo se você estava conectado antes da reinicialização do serviço. -### Problema: Status do DoubleZero está unknown ou down -Este problema está frequentemente relacionado ao túnel GRE sendo ativado com sucesso entre o servidor e o Dispositivo DoubleZero, mas um firewall impedindo o estabelecimento da sessão BGP. Por causa disso, você não está recebendo rotas da rede nem enviando tráfego pelo DoubleZero. +### Problema: Status do DoubleZero é unknown ou down +Este problema geralmente está relacionado ao túnel GRE sendo ativado com sucesso entre o servidor e o Dispositivo DoubleZero, mas um firewall impedindo o estabelecimento da sessão BGP. Por causa disso, você não está recebendo rotas da rede nem enviando tráfego pelo DoubleZero. **Sintomas:** - `doublezero connect ibrl` foi bem-sucedido. No entanto, `doublezero status` retorna `down` ou `unknown` + ``` + doublezero connect ibrl + DoubleZero Service Provisioning + 🔗 Start Provisioning User... + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + 🔍 Provisioning User for IP: 111.11.11.11 + User account created + Connected to device: nyc-dz001 + The user has been successfully activated + Service provisioned with status: ok + ✅ User Provisioned + ``` + + ```bash + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet + ``` **Soluções:** 1. Verifique suas regras de firewall! - O DoubleZero usa o espaço de endereços link local: 169.254.0.0/16 para as interfaces de túnel GRE entre sua máquina e o Dispositivo DoubleZero. 169.254.0.0/16 é tipicamente espaço "não roteável" e, portanto, as boas práticas de segurança recomendam bloquear as comunicações para/deste espaço. Você precisará permitir uma regra em seu firewall que habilite src 169.254.0.0/16 para se comunicar com dst 169.254.0.0/16 na porta tcp 179. Essa regra precisará ser colocada acima de quaisquer regras que Neguem tráfego para 169.254.0.0/16. + O DoubleZero usa o espaço de endereços link local: 169.254.0.0/16 para as interfaces do túnel GRE entre sua máquina e o Dispositivo DoubleZero. 169.254.0.0/16 é tipicamente um espaço "não roteável" e, portanto, boas práticas de segurança recomendam bloquear comunicações de/para este espaço. Você precisará permitir uma regra no seu firewall que habilite src 169.254.0.0/16 a se comunicar com dst 169.254.0.0/16 na porta tcp 179. Essa regra precisará ser colocada acima de quaisquer regras que neguem tráfego para 169.254.0.0/16. - Em um firewall como ufw você pode executar `sudo ufw status` para visualizar as regras do firewall. + Em um firewall como ufw você pode executar `sudo ufw status` para visualizar as regras do firewall e - Execute `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` para inserir a regra na posição . ou seja, se N = 1, então você inserirá esta regra como a primeira regra. - `sudo ufw status numbered` mostrará a ordenação numérica das regras. + Saída de Exemplo que pode ser algo similar ao que um validador Solana teria. + ``` + To Action From + -- ------ ---- + 22/tcp ALLOW Anywhere + 8899/tcp ALLOW Anywhere + 8000:10000/tcp ALLOW Anywhere + 8000:10000/udp ALLOW Anywhere + 11200:11300/udp ALLOW Anywhere + 11200:11300/tcp ALLOW Anywhere + + To Action From + -- ------ ---- + 10.0.0.0/8 DENY OUT Anywhere + 169.254.0.0/16 DENY OUT Anywhere + 172.16.0.0/12 DENY OUT Anywhere + 192.168.0.0/16 DENY OUT Anywhere + ``` + Na saída acima, você pode ver que todo o tráfego para 169.254.0.0/16, exceto para as portas especificadas, é negado. + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` para inserir a regra na posição . ou seja, se N = 1 então você inserirá esta regra como a primeira regra. + `sudo ufw status numbered` mostrará a ordenação numérica das regras. + ### Problema: O dispositivo DoubleZero mais próximo mudou -Isso não é um erro, mas pode ser uma otimização. Abaixo está uma prática recomendada que pode ser executada de tempos em tempos, ou automatizada. +Isso não é um erro, mas pode ser uma otimização. Abaixo está uma boa prática que pode ser executada periodicamente ou automatizada. **Soluções:** 1. Verifique a latência para o dispositivo mais próximo - - Execute `doublezero latency` + - execute `doublezero latency` + + saída + ``` + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + ``` + note acima que o dispositivo mais próximo é `dz-ny7-sw01` + + Queremos conectar a este dispositivo.: 2. Determine se você já está conectado ao dispositivo alvo - - Execute `doublezero user list --env testnet | grep 111.11.11.11` substituindo `111.11.11.11` pelo endereço IPv4 público do seu dispositivo conectado ao DoubleZero. + - execute `doublezero user list --env testnet | grep 111.11.11.11` substitua `111.11.11.11` pelo endereço IPv4 público do seu dispositivo que está conectado ao DoubleZero. Você também pode usar seu ID de validador ou ID DoubleZero. + + saída + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + Neste exemplo, já estamos conectados ao dispositivo mais próximo. Nenhum passo adicional é necessário, podemos parar aqui. + + + Vamos considerar, em vez disso, se a saída fosse + ``` + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + ``` + Esta seria uma conexão sub-ótima. Vamos considerar se a reconexão é necessária. + + Antes de conectar, verificaremos se o dispositivo possui túneis de usuário disponíveis. 3. Opcional: examine a rede para dispositivos disponíveis - - Execute `doublezero device list` para uma lista completa de dispositivos. -4. Determine se o dispositivo alvo tem uma conexão disponível - - Execute `doublezero device list | grep dz-ny7-sw01` substituindo `dz-ny7-sw01` pelo seu dispositivo alvo. + Para fins educacionais, primeiro: + - execute `doublezero device list` para uma lista completa de dispositivos. Selecionamos 2 dispositivos como exemplo para explicar a saída. + + saída: + ``` + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + ``` + Note acima que `ams001-dz002` tem 69 usuários e 128 usuários máximos. Este dispositivo pode adicionar 59 usuários. + + No entanto, `dz-fr5-sw01` tem 0 usuários e 0 usuários máximos. Você não poderá conectar a este dispositivo. Com max users igual a 0, o dispositivo não está aceitando nenhuma conexão. + + Agora vamos retornar à conexão com nosso dispositivo mais próximo. -5. Conecte-se ao Dispositivo DoubleZero mais próximo +4. Determine se o dispositivo alvo possui uma conexão disponível + - execute `doublezero device list | grep dz-ny7-sw01` substitua `dz-ny7-sw01` pelo seu dispositivo alvo - Desconecte e reconecte ao DoubleZero: + saída + ``` + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + ``` + aqui podemos ver que `dz-ny7-sw01` tem espaço disponível para conexão. + +5. Conecte ao Dispositivo DoubleZero mais próximo + + Vamos desconectar e depois reconectar ao DoubleZero. + + Primeiro execute - `doublezero disconnect` - - `doublezero status` (confirme a desconexão) + + saída + + ``` + DoubleZero Service Provisioning + 🔍 Decommissioning User + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + \ [00:00:00] [##########>-----------------------------] 1/4 deleting user account... 🔍 Deleting User Account for: 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW + 🔍 User Account deleted + ✅ Deprovisioning Complete + ``` + agora verificamos o status para confirmar nossa desconexão com + - `doublezero status` + + saída + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | + ``` + Por último, vamos reconectar com - `doublezero connect ibrl` + saída + ``` + DoubleZero Service Provisioning + 🔗 Start Provisioning User... + Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` + 🔍 Provisioning User for IP: 111.11.11.11 + User account created + Connected to device: dz-ny7-sw01 + Service provisioned with status: ok + ✅ User Provisioned + ``` + observe na saída acima que nos `Connected to device: dz-ny7-sw01` este é o resultado desejado da nossa investigação inicial no passo 1, onde descobrimos que `dz-ny7-sw01` era o dispositivo com a menor latência. + ### Problema: `doublezero status` retorna alguns campos com N/A -Este problema está geralmente relacionado a uma incompatibilidade entre o daemon e cliente atuais versus o daemon e cliente em que o túnel DZ conectado foi estabelecido. +Este problema geralmente está relacionado a uma incompatibilidade entre o daemon e cliente atuais, em relação ao daemon e cliente com os quais o túnel DZ conectado foi estabelecido. **Sintomas:** -- Ao executar `doublezero status`, o usuário encontra `N/A` em alguns campos +- Ao executar `doublezero status` o usuário encontra `N/A` em alguns campos + + + **Soluções:** -1. Execute `doublezero status` e examine a saída. +1. Execute +`doublezero status` + + Exemplo: + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta + ``` - Se `Current Device` e `Metro` são `N/A`, mas o status do túnel está `up`, isso indica que seu túnel aberto está em um ambiente diferente do configurado atualmente. + Observe na nossa saída de exemplo acima que o `Tunnel status` é `up`. Nossa `Network` é `mainnet-beta`. No entanto, `Current Device` e `Metro` são `N/A` -2. Mude seu ambiente para o oposto do ambiente que retorna `N/A`: + Isso é indicativo de um túnel aberto na sua máquina que não está no seu ambiente atual. + Neste caso, o status `up`, sem `Current Device` encontrado em `mainnet-beta`, nos revela que nosso túnel está no testnet! + +2. Mude seu ambiente. + + Para corrigir a incompatibilidade, você mudará seu ambiente para o oposto do ambiente que está retornando `N/A` ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -209,10 +354,30 @@ Este problema está geralmente relacionado a uma incompatibilidade entre o daemo && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` -3. Verifique seu status após mudar os ambientes: + Para configurar o CLI do Cliente DoubleZero (`doublezero`) e o daemon (`doublezerod`) para conectar ao **DoubleZero mainnet-beta**: + + ```bash + DESIRED_DOUBLEZERO_ENV=mainnet-beta \ + && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \ + && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \ + && sudo systemctl daemon-reload \ + && sudo systemctl restart doublezerod \ + && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ + && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" + ``` + +3. Verifique seu status + + Após trocar de ambiente, execute: ``` doublezero status ``` - Com todos os campos preenchidos, você está agora no ambiente correto. + A saída esperada deve ser similar a: + + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet + ``` +Com todos os campos preenchidos, você agora está no ambiente correto. \ No newline at end of file diff --git a/docs/troubleshooting.zh.md b/docs/troubleshooting.zh.md index 263b843..0a74266 100644 --- a/docs/troubleshooting.zh.md +++ b/docs/troubleshooting.zh.md @@ -1,13 +1,15 @@ -# 故障排除 -!!! warning "This translation was generated using artificial intelligence and has not been reviewed by a human translator. It may contain inaccuracies or errors and should not be relied upon." +--- +description: 诊断常见的 DoubleZero 连接问题,包含参考命令、预期输出以及获取进一步支持的途径。 +--- +# 故障排除 -本指南将涵盖各种问题,并持续更新。如果您完成了本指南仍需进一步支持,可以在[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discord中寻求帮助。 +本指南将涵盖各种问题,并持续更新。如果您完成了本指南仍未解决问题,可以在 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) Discord 中寻求进一步支持。 -## 常用命令和输出 +## 常用命令与输出 -首先检查以下命令的输出及其预期输出。这将帮助您进行更详细的故障排除。 +首先,检查以下命令的输出及其预期输出。这些将帮助您进行更详细的故障排除。 如果您提交工单,可能会被要求提供这些输出。 #### 1. 检查版本 @@ -19,8 +21,9 @@ ``` DoubleZero 0.6.3 ``` +[comment]: # (when repo is public add this link to check https://github.com/malbeclabs/doublezero) -#### 2. 检查DoubleZero地址 +#### 2. 检查 DoubleZero 地址 命令: `doublezero address` @@ -29,23 +32,24 @@ DoubleZero 0.6.3 ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` +[comment]: # () -#### 3. 验证您的访问通行证 +#### 3. 验证您的 Access Pass -示例公钥:`MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` 运行命令时请替换为您的公钥。 +示例公钥:`MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2`,运行命令时请替换为您的公钥。 命令: `doublezero access-pass list | grep MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2` -输出:[注意我们使用`doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'`在此输出中显示标题] +输出:[注意我们使用 `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` 来在输出中显示表头] ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner -2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn +2XHCWm8Sef1GirhAhAJVA8WTXToPT6gFYP7fA9mWMShR | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - -#### 4. 检查DoubleZero账本余额 +[comment]: # () +#### 4. 检查 DoubleZero 账本余额 命令: `doublezero balance` @@ -54,6 +58,7 @@ account | accesspass_type ``` 0.78 Credits ``` +[comment]: # (add section linked later for 0 balance mainnet/testnet) #### 5. 检查连接状态 命令: @@ -63,9 +68,10 @@ account | accesspass_type 示例输出: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` +[comment]: # (in next iteration add "up" "unknown" and "down" explainers, which then link to a sectino below for troubleshooting undesired states.) #### 6. 检查延迟 @@ -75,7 +81,7 @@ account | accesspass_type 示例输出: ``` - pubkey | code | ip | min | max | avg | reachable + pubkey | code | ip | min | max | avg | reachable 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001 | 64.86.249.22 | 2.49ms | 2.61ms | 2.56ms | true Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001 | 195.219.120.66 | 71.94ms | 72.11ms | 72.02ms | true CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001 | 207.45.216.134 | 72.42ms | 72.51ms | 72.45ms | true @@ -86,16 +92,17 @@ account | accesspass_type 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001 | 180.87.154.78 | 178.04ms | 178.23ms | 178.13ms | true D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001 | 180.87.102.98 | 227.67ms | 227.85ms | 227.75ms | true ``` +[comment]: # () # 故障排除示例 -现在我们已检查了基本输出及健康部署中的预期内容,可以检查一些常见的故障排除示例。 +既然我们已经检查了基本输出以及健康部署中的预期结果,接下来我们可以看一些常见的故障排除示例。 -### 问题:❌ 创建用户时出错 +### 问题:❌ Error creating user -此问题通常与预期的公钥/IP配对与用户尝试访问DoubleZero的公钥/IP配对不匹配有关。 +此问题通常与预期的公钥/IP 配对和用户尝试访问 DoubleZero 时使用的公钥/IP 配对不匹配有关。 **症状:** -- 使用`doublezero connect ibrl`连接时,用户遇到`❌ Error creating user` +- 使用 `doublezero connect ibrl` 连接时,用户遇到 `❌ Error creating user` **解决方案:** @@ -107,7 +114,7 @@ account | accesspass_type ``` MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 ``` -2. 验证此地址已被列入白名单: +2. 验证该地址是否在允许列表中: `doublezero access-pass list | awk 'NR==1 || /MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2/'` @@ -115,19 +122,19 @@ account | accesspass_type ``` account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner - FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + FHyoPs7U23MuSTtepEyXUtSAEffEpFpJGoYvug8X2sWY | prepaid | 141.14.14.14 | MTAwoHgKyTwwDGJo2dye6EWqyTn27JRwXxaDEaeMqe2 | MAX | MAX | 0 | requested | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - `doublezero address`的公钥必须与user_payer公钥匹配,且您尝试连接的IP地址必须与访问通行证中的ip匹配。 - `doublezero address`来源于默认情况下`~/.config/doublezero/`中的id.json文件。请参阅[此处的步骤6](https://docs.malbeclabs.com/setup/) - -3. 如果上述内容看起来正确但您在连接时遇到错误,或者如果上述映射不正确,请在[DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701)联系支持 + `doublezero address` 的公钥必须与 user_payer 公钥匹配,并且您尝试连接的 IP 地址必须与 Access-Pass 中的 ip 匹配。 + `doublezero address` 默认从 ~/.config/doublezero/ 中的 id.json 文件获取。参见[此处的步骤 6]() + +3. 如果以上信息看起来正确但您在连接时仍然出错,或者上述映射不正确,请在 [DoubleZero Tech](https://discord.com/channels/1341597747932958802/1344323790464880701) 联系支持团队 -### 问题:❌ 配置服务时出错:格式错误:无法同时配置多个隧道 -此错误表示设备已连接到DoubleZero。 +### 问题:❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time +此错误表示设备已经连接到 DoubleZero。 **症状:** -- 用户尝试连接到DoubleZero -- 遇到`❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time`。 +- 用户尝试连接 DoubleZero +- 遇到 `❌ Error provisioning service: malformed stuff: cannot provision multiple tunnels at the same time` **解决方案:** 1. 检查 @@ -135,22 +142,22 @@ account | accesspass_type 输出: ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` -2. `up`表示连接正常。 -3. 出现此错误是因为具有特定DoubleZero IP的到DoubleZero的隧道已在此机器上处于活跃状态。 +2. -`up`- 表示连接健康。 +3. 此错误出现是因为使用特定 DoubleZero IP 到 DoubleZero 的隧道在此机器上已经处于活动状态。 - 此错误通常在DoubleZero客户端升级后遇到。DoubleZero升级会自动重启doublezerod服务,如果您在服务重启之前已连接,将会重新连接您。 + 此错误通常在 DoubleZero 客户端升级后遇到。DoubleZero 升级会自动重启 doublezerod 服务,如果您在服务重启前已连接,升级后会自动重新连接。 -### 问题:DoubleZero状态为unknown或down -此问题通常与服务器和DoubleZero设备之间的GRE隧道已成功激活,但防火墙阻止BGP会话建立有关。因此,您没有从网络接收路由或通过DoubleZero发送流量。 +### 问题:DoubleZero 状态为 unknown 或 down +此问题通常与 GRE 隧道在服务器和 DoubleZero 设备之间已成功激活有关,但防火墙阻止了 BGP 会话的建立。因此,您无法从网络接收路由,也无法通过 DoubleZero 发送流量。 **症状:** -- `doublezero connect ibrl`成功。但是,`doublezero status`返回`down`或`unknown` +- `doublezero connect ibrl` 执行成功。但是,`doublezero status` 返回 `down` 或 `unknown` ``` - doublezero connect ibrl + doublezero connect ibrl DoubleZero Service Provisioning 🔗 Start Provisioning User... Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` @@ -163,18 +170,18 @@ account | accesspass_type ``` ```bash - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network up | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL | ams-dz001 | ✅ ams-dz001 | Amsterdam | testnet ``` **解决方案:** 1. 检查您的防火墙规则! - DoubleZero使用链路本地地址空间:169.254.0.0/16用于您的机器和DoubleZero设备之间的GRE隧道接口。169.254.0.0/16通常是"不可路由"的地址空间,因此良好的安全实践会建议您阻止与此地址空间之间的通信。您需要在防火墙中允许一条规则,使src 169.254.0.0/16可以与dst 169.254.0.0/16在tcp端口179上通信。该规则需要放在任何拒绝169.254.0.0/16流量的规则之上。 + DoubleZero 使用链路本地地址空间 169.254.0.0/16 作为您的机器与 DoubleZero 设备之间的 GRE 隧道接口。169.254.0.0/16 通常是"不可路由"的地址空间,因此良好的安全实践会建议您阻止与此地址空间的通信。您需要在防火墙中添加一条规则,允许源 169.254.0.0/16 与目的 169.254.0.0/16 在 tcp 端口 179 上进行通信。该规则需要放在任何拒绝 169.254.0.0/16 流量的规则之前。 - 在ufw等防火墙中,您可以运行`sudo ufw status`查看防火墙规则,以及 + 在像 ufw 这样的防火墙中,您可以运行 `sudo ufw status` 来查看防火墙规则 - 示例输出,可能类似于Solana验证器会有的内容: + 示例输出,可能与 Solana 验证节点的配置类似: ``` To Action From -- ------ ---- @@ -193,47 +200,47 @@ account | accesspass_type 192.168.0.0/16 DENY OUT Anywhere ``` - 在上面的输出中,您可以看到除了指定端口外,所有到169.254.0.0/16的流量都被拒绝。 - `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179`将规则插入到位置。即如果N=1,则将此规则插入为第一条规则。 - `sudo ufw status numbered`将显示规则的数字排序。 + 在上述输出中,您可以看到除指定端口外,所有到 169.254.0.0/16 的流量都被拒绝。 + `sudo ufw insert allow proto tcp from 169.254.0.0/16 to 169.254.0.0/16 port 179` 将规则插入到第 个位置。例如,如果 N = 1,则将此规则插入为第一条规则。 + `sudo ufw status numbered` 将显示规则的数字排序。 + +### 问题:最近的 DoubleZero 设备已更改 -### 问题:最近的DoubleZero设备已更改 - -这不是一个错误,但可以是一种优化。以下是可以不时运行或自动化的最佳实践。 +这不是一个错误,而是一个优化建议。以下是一个最佳实践,可以不时手动运行或设置自动化。 **解决方案:** 1. 检查到最近设备的延迟 - - 运行`doublezero latency` + - 运行 `doublezero latency` 输出 ``` - pubkey | code | ip | min | max | avg | reachable - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true - ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true - 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true - 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true - FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true + pubkey | code | ip | min | max | avg | reachable + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | 137.239.213.162 | 1.80ms | 1.90ms | 1.84ms | true + ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01 | 137.239.213.170 | 1.83ms | 2.10ms | 1.92ms | true + 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02 | 142.215.184.122 | 1.87ms | 2.66ms | 2.15ms | true + 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002 | 38.122.35.137 | 2.33ms | 2.39ms | 2.37ms | true + FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001 | 38.104.167.29 | 2.29ms | 2.59ms | 2.40ms | true ``` - 注意上面最近的设备是`dz-ny7-sw01` + 注意上方最近的设备是 `dz-ny7-sw01` - 我们想要连接到此设备。: + 我们要连接到这个设备。 -2. 确定您是否已连接到目标设备 - - 运行`doublezero user list --env testnet | grep 111.11.11.11`,将`111.11.11.11`替换为您已连接到DoubleZero的设备的公共IPv4地址。您也可以使用您的验证器ID或DoubleZero ID。 +2. 确认您是否已经连接到目标设备 + - 运行 `doublezero user list --env testnet | grep 111.11.11.11`,将 `111.11.11.11` 替换为连接到 DoubleZero 的设备公网 IPv4 地址。您也可以使用您的验证节点 ID 或 doublezero ID。 输出 ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | dz-ny7-sw01 | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` - 在此示例中,我们已连接到最近的设备。无需更多步骤,我们可以在这里停止。 + 在此示例中,我们已经连接到最近的设备。无需更多步骤,可以在此停止。 - 让我们考虑如果输出为 + 让我们考虑如果输出是这样的情况 ``` - account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner - 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn + account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner + 6QRU1ivJnKGHpom2BdzH9PiTRkJ5WhunPNLtfYcqVisW | IBRL | | fra-dz-001-x | New York | GREOverDIA | 111.11.11.11 | 111.11.11.11 | Prepaid: (MAX) | 514 | 111.254.1.111/31 | activated | DZfHh2vjXFqt8zfNbT1afm8PGuCm3BrQKegC5THtKFdn ``` 这将是一个次优连接。让我们考虑是否需要重新连接。 @@ -241,33 +248,33 @@ account | accesspass_type 3. 可选:检查网络中的可用设备 - 出于教育目的,我们将首先: - - 运行`doublezero device list`获取完整的设备列表。我们以2个设备为例来解释输出。 + 出于教学目的,我们首先: + - 运行 `doublezero device list` 获取完整的设备列表。我们提取了 2 个设备作为示例来解释输出。 输出: ``` - account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner - GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp - 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + account | code | contributor | location | exchange | device_type | public_ip | dz_prefixes | users | max_users | status | mgmt_vrf | owner + GphgLkA7JDVtkDQZCiDrwrDvaUs8r8XczEae1KkV6CGQ | ams001-dz002 | jump_ | EQX-AM4 | ams | switch | 149.11.64.57 | 38.246.201.64/27 | 69 | 128 | activated | | H647kAwTcWsGXZUK3BTr1JyTBZmbNcYyCmRFFCEnXUVp + 7FfrX8YbvbzM8A1ojNynP9BjiKpK9rrmhdEdchB2myhG | dz-fr5-sw01 | glxy | EQX-FR5 | fra | switch | 89.222.118.225 | 89.222.118.228/30 | 0 | 0 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - 注意上面`ams001-dz002`有69个用户,最多128个用户。此设备可以再增加59个用户。 + 注意上方 `ams001-dz002` 有 69 个用户,最大用户数为 128。该设备还可以添加 59 个用户。 - 但是,`dz-fr5-sw01`有0个用户,0个最大用户。您将无法连接到此设备。最大用户为0,设备不接受任何连接。 + 然而,`dz-fr5-sw01` 有 0 个用户,最大用户数也为 0。您将无法连接到此设备。最大用户数为 0 意味着该设备不接受任何连接。 - 现在让我们回到连接到最近的设备。 + 现在让我们回到连接最近设备的操作。 -4. 确定目标设备是否有可用连接 - - 运行`doublezero device list | grep dz-ny7-sw01`,将`dz-ny7-sw01`替换为您的目标设备 +4. 确认目标设备是否有可用连接 + - 运行 `doublezero device list | grep dz-ny7-sw01`,将 `dz-ny7-sw01` 替换为您的目标设备 输出 ``` - 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp + 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01 | glxy | EQX-NY7 | nyc | switch | 137.239.213.162 | 137.239.216.164/31 | 29 | 128 | activated | | 5YbNrJHJJoiRwVEvgAWRGdFRG9gRdZ47hLCKSym8bqbp ``` - 这里我们可以看到`dz-ny7-sw01`有可用的连接空间。 + 在这里我们可以看到 `dz-ny7-sw01` 有可用的连接空间。 -5. 连接到最近的DoubleZero设备 +5. 连接到最近的 DoubleZero 设备 - 我们将断开连接,然后重新连接到doublezero。 + 我们将先断开连接,然后重新连接到 DoubleZero。 首先运行 - `doublezero disconnect` @@ -282,16 +289,16 @@ account | accesspass_type 🔍 User Account deleted ✅ Deprovisioning Complete ``` - 现在我们用以下命令检查状态以确认断开连接 + 现在我们检查状态以确认断开连接 - `doublezero status` 输出 ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type - disconnected | no session data | | | | | + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type + disconnected | no session data | | | | | ``` - 最后我们将重新连接 + 最后我们重新连接 - `doublezero connect ibrl` 输出 @@ -301,18 +308,18 @@ account | accesspass_type Public IP detected: 111.11.11.11 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x` 🔍 Provisioning User for IP: 111.11.11.11 User account created - Connected to device: dz-ny7-sw01 + Connected to device: dz-ny7-sw01 Service provisioned with status: ok ✅ User Provisioned ``` - 注意上面的输出中我们`Connected to device: dz-ny7-sw01`,这是我们在步骤1初始调查中发现`dz-ny7-sw01`是延迟最低设备的期望结果。 + 注意上方输出中我们 `Connected to device: dz-ny7-sw01`,这是我们在步骤 1 中初始调查的预期结果,当时我们发现 `dz-ny7-sw01` 是延迟最低的设备。 -### 问题:`doublezero status`返回某些字段为N/A +### 问题:`doublezero status` 部分字段返回 N/A -此问题通常与当前守护程序和客户端与建立已连接DZ隧道时的守护程序和客户端不匹配有关。 +此问题通常与当前的守护进程和客户端与建立已连接 DZ 隧道时的守护进程和客户端不匹配有关。 **症状:** -- 运行`doublezero status`时,用户在某些字段中遇到`N/A` +- 运行 `doublezero status` 时,用户在某些字段中遇到 `N/A` @@ -328,14 +335,14 @@ account | accesspass_type up | 2025-10-20 20:06:18 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | N/A | ✅ dz-ny7-sw01 | N/A | mainnet-beta ``` - 注意在上面的示例输出中,`Tunnel status`为`up`。我们的`Network`是`mainnet-beta`。但是,`Current Device`和`Metro`是`N/A`。 - - 这表明您的机器上有一个开放隧道,它不在您当前的环境中。 - 在这种情况下,在`mainnet-beta`上找不到`Current Device`的`up`状态告诉我们我们的隧道在测试网上! + 注意我们上方的示例输出中 `Tunnel status` 为 `up`。我们的 `Network` 是 `mainnet-beta`。然而,`Current Device` 和 `Metro` 为 `N/A` -2. 更改您的环境。 + 这表明您的机器上有一个打开的隧道不属于您当前的环境。 + 在这种情况下,`up` 状态以及在 `mainnet-beta` 上未找到 `Current Device` 告诉我们隧道实际在 testnet 上! + +2. 切换您的环境。 - 为了纠正不匹配,您将把环境更改为返回`N/A`的相反环境。 + 为了纠正这种不匹配,您需要将环境切换到返回 `N/A` 的环境的另一个环境 ```bash DESIRED_DOUBLEZERO_ENV=testnet \ @@ -347,7 +354,7 @@ account | accesspass_type && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - 要配置DoubleZero客户端CLI(`doublezero`)和守护程序(`doublezerod`)连接到**DoubleZero主网Beta**: + 要将 DoubleZero 客户端 CLI (`doublezero`) 和守护进程 (`doublezerod`) 配置为连接到 **DoubleZero mainnet-beta**: ```bash DESIRED_DOUBLEZERO_ENV=mainnet-beta \ @@ -358,7 +365,7 @@ account | accesspass_type && doublezero config set --env $DESIRED_DOUBLEZERO_ENV > /dev/null \ && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV" ``` - + 3. 检查您的状态 切换环境后运行: @@ -369,8 +376,8 @@ account | accesspass_type 预期输出应类似于: + ``` + Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network + up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet ``` - Tunnel status | Last Session Update | Tunnel Name | Tunnel src | Tunnel dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro | Network - up | 2025-10-21 12:32:12 UTC | doublezero0 | 149.28.38.64 | 64.86.249.22 | 149.28.38.64 | IBRL | nyc-dz001 | ✅ nyc-dz001 | New York | testnet - ``` -所有字段都已填充,您现在处于正确的环境中。 +所有字段都已填充,说明您现在处于正确的环境中。 \ No newline at end of file diff --git a/hooks/emit_well_known.py b/hooks/emit_well_known.py index 1d8ad6e..8e138a4 100644 --- a/hooks/emit_well_known.py +++ b/hooks/emit_well_known.py @@ -29,7 +29,7 @@ import shutil SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json" -SITE_BASE = "https://docs.malbeclabs.com" +SITE_BASE = "https://docs.doublezero.xyz" def _repo_root(config) -> str: diff --git a/mkdocs.yml b/mkdocs.yml index 89a0c59..d5495b7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ -site_name: Malbec Labs -site_url: https://docs.malbeclabs.com/ +site_name: DoubleZero +site_url: https://docs.doublezero.xyz/ site_description: >- Documentation for connecting to and operating on the Malbec Labs / DoubleZero network — validator and tenant setup, multicast, rewards, geolocation, and diff --git a/scripts/build-vercel-output.sh b/scripts/build-vercel-output.sh new file mode 100755 index 0000000..13b71f6 --- /dev/null +++ b/scripts/build-vercel-output.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Populate .vercel/output/ from a tar of the mkdocs site directory. +# +# Takes the SAME tar that is uploaded to GitHub Pages, so both origins are +# guaranteed to serve byte-identical content. +# +# Usage: scripts/build-vercel-output.sh +set -euo pipefail + +SITE_TAR="${1:?usage: build-vercel-output.sh }" + +rm -rf .vercel/output +mkdir -p .vercel/output/static + +# The tar is created with `-C site .`, so it extracts as the site root. +# Using tar (not cp) is deliberate: it preserves the .well-known dot-directory, +# which shell globs and some copy tools silently skip. +tar -xf "$SITE_TAR" -C .vercel/output/static + +# trailingSlash matches mkdocs use_directory_urls, so /setup and /setup/ do not +# become two distinct URLs. The error route serves mkdocs' own 404.html. +cat > .vercel/output/config.json <<'JSON' +{ + "version": 3, + "trailingSlash": true, + "routes": [ + { "handle": "error" }, + { "src": "/.*", "status": 404, "dest": "/404.html" } + ] +} +JSON + +echo "--- .vercel/output/static top level ---" +ls -a .vercel/output/static | head -20 +test -d .vercel/output/static/.well-known || { echo "FATAL: .well-known missing"; exit 1; } +echo "OK: .well-known present" diff --git a/site/404.html b/site/404.html deleted file mode 100644 index 0e703ef..0000000 --- a/site/404.html +++ /dev/null @@ -1,1008 +0,0 @@ - - - - - - - - - - - - - - - - - - - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- -

404 - Not found

- -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/CNAME b/site/CNAME deleted file mode 100644 index ec4ef4f..0000000 --- a/site/CNAME +++ /dev/null @@ -1 +0,0 @@ -docs.malbeclabs.com \ No newline at end of file diff --git a/site/DZ Mainnet-beta Connection/index.html b/site/DZ Mainnet-beta Connection/index.html deleted file mode 100644 index dd33ce0..0000000 --- a/site/DZ Mainnet-beta Connection/index.html +++ /dev/null @@ -1,1417 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Validator Mainnet-Beta Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Validator Mainnet-Beta Connection in IBRL Mode

-
-

By connecting to DoubleZero I agree to the DoubleZero Terms of Service

-
-

Connecting to Mainnet-Beta in IBRL Mode

-
-

Note

-

IBRL mode does not require restarting validator clients, because it uses your existing public IP address.

-
-

Solana Mainnet Validators will complete connection to DoubleZero Mainnet-beta, which is detailed on this page.

-

Each Solana validator has its own identity keypair; from this, extract the public key known as the node ID. This is the validator's unique fingerprint on the Solana network.

-

With the DoubleZeroID and node ID identified, you will prove ownership of your machine. This is done by creating a message which includes the DoubleZeroID signed with the validator's identity key. The resulting cryptographic signature serves as verifiable proof that you control the validator.

-

Finally, you will submit a connection request to DoubleZero. This request communicates: "Here is my identity, here is proof of ownership, and here is how I intend to connect." DoubleZero validates this information, accepts the proof, and provisions network access for the validator on DoubleZero.

-

This guide allows for 1 Primary Validator to register itself, and up to 3 backup/failover machines at the same time.

-

Prerequisites

-
    -
  • Solana CLI installed and on $PATH
  • -
  • For validators: Permission to access to the validator identity keypair file (e.g., validator-keypair.json) under the sol user
  • -
  • For validators: Verify the Identity key of Solana validator being connected has at least 1 SOL on it
  • -
  • Firewall rules permit outbound connections for DoubleZero and Solana RPC as needed including - GRE (ip proto 47) and BGP (169.254.0.0/16 on tcp/179)
  • -
-
-

Info

-

The Validator ID will be checked against Solana gossip to determine the target IP. The target IP, and the DoubleZero ID will then be used when opening a GRE tunnel between your machine and the target DoubleZero Device.

-

Consider: In the case where you have a junk ID and Primary ID on at the same IP, only the Primary ID will be used in registration of the machine. This is because the junk ID will not appear in gossip, and therefore cannot be used to verify the IP of the target machine.

-
-

1. Environment Configuration

-

Please follow the setup instructions before proceeding.

-

The last step in setup was to disconnect from the network. This is to ensure that only one tunnel is open on your machine to DoubleZero, and that tunnel is on the correct network.

-
-

To configure the DoubleZero Client CLI (doublezero) and daemon (doublezerod) to connect to DoubleZero mainnet-beta: -

DESIRED_DOUBLEZERO_ENV=mainnet-beta \
-    && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \
-    && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \
-    && sudo systemctl daemon-reload \
-    && sudo systemctl restart doublezerod \
-    && doublezero config set --env $DESIRED_DOUBLEZERO_ENV  > /dev/null \
-    && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV"
-

-

You should see the following output: -✅ doublezerod configured for environment mainnet-beta

-

After about 30 seconds you will see the DoubleZero devices available:

-

doublezero latency
-
-Example output (Mainnet-Beta) -
 pubkey                                       | code          | ip              | min      | max      | avg      | reachable
- 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01   | 137.239.213.162 | 1.74ms   | 1.92ms   | 1.84ms   | true
- ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01   | 137.239.213.170 | 1.88ms   | 4.39ms   | 2.72ms   | true
- 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002  | 38.122.35.137   | 2.45ms   | 3.30ms   | 2.74ms   | true
- 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02   | 142.215.184.122 | 1.88ms   | 5.13ms   | 3.02ms   | true
- uzyg9iYw2FEbtdTHaDb5HoeEWYAPRPQgvsgyd873qPS  | nyc001-dz002  | 4.42.212.122    | 3.17ms   | 3.63ms   | 3.33ms   | true
- FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001  | 38.104.167.29   | 2.33ms   | 5.46ms   | 3.39ms   | true
- 9oKLaL6Hwno5TyAFutTbbkNrzxm1fw9fhzkiUHgsxgGx | dz-dc10-sw01  | 137.239.200.186 | 6.84ms   | 7.01ms   | 6.91ms   | true
- DESzDP8GkSTpQLkrUegLkt4S2ynGfZX5bTDzZf3sEE58 | was001-dz002  | 38.88.214.133   | 7.39ms   | 7.44ms   | 7.41ms   | true
- HHNCpqB7CwHVLxAiB1S86ko6gJRzLCtw78K1tc7ZpT5P | was001-dz001  | 66.198.11.74    | 7.67ms   | 7.85ms   | 7.76ms   | true
- 9LFtjDzohKvCBzSquQD4YtL3HwuvkKBDE7KSzb8ztV2b | dz-mtl11-sw01 | 134.195.161.10  | 9.88ms   | 10.01ms  | 9.95ms   | true
- 9M7FfYYyjM4wGinKPofZRNmQFcCjCKRbXscGBUiXvXnG | dz-tor1-sw01  | 209.42.165.10   | 14.52ms  | 14.53ms  | 14.52ms  | true
-
-Testnet output will be identical in structure, but with fewer devices. -

-
-

2. Open port 44880

-

Users need to open port 44880 to utilize some routing features.

-

To open port 44880 you could update IP tables such as:

-
-
sudo iptables -A INPUT -i doublezero0 -p udp --dport 44880 -j ACCEPT
-sudo iptables -A OUTPUT -o doublezero0 -p udp --dport 44880 -j ACCEPT
-
-
-

note the -i doublezero0, -o doublezero0 flags which restrict this rule to only the DoubleZero interface

-

Or UFW such as:

-
-
sudo ufw allow in on doublezero0 to any port 44880 proto udp
-sudo ufw allow out on doublezero0 to any port 44880 proto udp
-
-
-

note the in on doublezero0, out on doublezero0 flags which restrict this rule to only the DoubleZero interface

-

3. Attest Validator Ownership

-
-

With your DoubleZero Environment set, it is now time to attest to your Validator Ownership.

-

The DoubleZero ID you created in the setup of your primary validator must be used on all backup machines.

-

The ID on your primary machine can be found with doublezero address The same ID must be in ~/.config/doublezero/id.json on all machines in the cluster.

-

In order to accomplish this you will first verify the machine you are running the commands from is your Primary Validator with:

-
doublezero-solana passport find-validator -u mainnet-beta
-
-

This verifies that the validator is registered in gossip and appears in the leader schedule.

-

Expected output:

-
Connected to Solana: mainnet
-
-DoubleZero ID: YourDoubleZeroAddress11111111111111111111111111111
-Detected public IP: 11.11.11.111
-Validator ID: ValidatorIdentity111111111111111111111111111
-Gossip IP: 11.11.11.111
-In Leader scheduler
-✅ This validator can connect as a primary in DoubleZero 🖥️  💎. It is a leader scheduled validator.
-
-
-

Info

-

The same workflow is used for one, or many machines. -To register one machine exclude the arguments "--backup-validator-ids" or "backup_ids=" from any commands on this page.

-
-

Now, on all backup machines you intend to run your Primary Validator on execute the following: -

doublezero-solana passport find-validator -u mainnet-beta
-

-

Expected output:

-

Connected to Solana: mainnet
-
-DoubleZero ID: YourDoubleZeroAddress11111111111111111111111111111
-Detected public IP: 22.22.22.222
-Validator ID: ValidatorIdentity222222222222222222222222222
-Gossip IP: 22.22.22.222
-In Not in Leader scheduler
- ✅ This validator can only connect as a backup in DoubleZero 🖥️  🛟. It is not leader scheduled and cannot act as a primary validator.
-
-This output is expected. The backup node cannot be in the leader schedule at time of pass creation.

-

You will now run this command on all backup machines you plan to use your Primary Validator vote account, and identity on.

-
-
-

Prepare the Connection

-

Run the following command on the Primary Validator machine. This is the machine you have active stake on, that is in the leader schedule with your primary validator ID in solana gossip on the machine you are running the command from:

-
doublezero-solana passport prepare-validator-access -u mainnet-beta \
-  --doublezero-address YourDoubleZeroAddress11111111111111111111111111111 \
-  --primary-validator-id ValidatorIdentity111111111111111111111111111 \
-  --backup-validator-ids ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444>
-
-

Example output:

-

DoubleZero Passport - Prepare Validator Access Request
-Connected to Solana: mainnet-beta
-
-Primary validator 🖥️  💎:
-  ID: ValidatorIdentity111111111111111111111111111
-  Gossip: ✅ OK 11.11.11.111)
-  Leader scheduler: ✅ OK (Stake: 1,050,000.00 SOL)
-
-Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity222222222222222222222222222
-  Gossip: ✅ OK (22.22.22.222)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-
-Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity333333333333333333333333333
-  Gossip: ✅ OK (33.33.33.333)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-
-  Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity444444444444444444444444444
-  Gossip: ✅ OK (33.33.33.333)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-  To request access, sign the following message with your validator's identity key:
-
-  solana sign-offchain-message \
-     service_key=YourDoubleZeroAddress11111111111111111111111111111,backup_ids=ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
-     -k <identity-keypair-file.json>
-
-Note the output at the end of this command. It is the structure for the next step.

-
-

4. Generate Signature

-
-

At the end of the last step, we received a pre-formatted output for solana sign-offchain-message

-

From the above output we will run this command on the Primary Validator machine.

-
  solana sign-offchain-message \
-     service_key=YourDoubleZeroAddress11111111111111111111111111111,backup_ids=ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
-     -k <identity-keypair-file.json>
-
-

Output:

-
  Signature111111rrNykTByK2DgJET3U6MdjSa7xgFivS9AHyhdSG6AbYTeczUNJSjYPwBGqpmNGkoWk9NvS3W7
-
-
-

5. Initiate a Connection Request in DoubleZero

-
-

Use the request-validator-access command to create an account on Solana for the connection request. The DoubleZero Sentinel agent detects the new account, validates its identity and signature, and creates the access pass in DoubleZero so the server can establish a connection.

-

Use the node ID, DoubleZeroID, and signature.

-
-

Note

-

In this example we use -k /home/user/.config/solana/id.json to find the validator Identity. Use the appropriate location for your local deployment.

-
-
doublezero-solana passport request-validator-access -k <path to keypair> -u mainnet-beta \
---primary-validator-id ValidatorIdentity111111111111111111111111111 \
---backup-validator-ids ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
---signature Signature111111rrNykTByK2DgJET3U6MdjSa7xgFivS9AHyhdSG6AbYTeczUNJSjYPwBGqpmNGkoWk9NvS3W7 --doublezero-address YourDoubleZeroAddress11111111111111111111111111111
-
-

Output:

-

This output can be used to see the transaction on a Solana explorer. Be sure to change the explorer to mainnet. This verification is optional.

-
Request Solana validator access: Transaction22222222VaB8FMqM2wEBXyV5THpKRXWrPtDQxmTjHJHiAWteVYTsc7Gjz4hdXxvYoZXGeHkrEayp
-
-

If successful, DoubleZero will register the primary with its backups. You may now failover between the IPs registered in the access pass. DoubleZero will maintain connectivity automatically when switching to backup nodes registered in this way.

-
-

6. Connect in IBRL Mode

-
-

On the server, with the user which will connect to DoubleZero, run the connect command to establish the connection to DoubleZero.

-
doublezero connect ibrl
-
-

You should see output indicating provisioning, such as:

-

DoubleZero Service Provisioning
-🔗  Start Provisioning User...
-Public IP detected: 137.184.101.183 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x`
-🔍  Provisioning User for IP: 137.184.101.183
-    User account created
-    Connected to device: nyc-dz001
-    The user has been successfully activated
-    Service provisioned with status: ok
-✅  User Provisioned
-
-Wait one minute for the GRE tunnel to finish setting up. Until the GRE tunnel is done setting up, your status output may return "down" or "Unknown"

-

Verify your connection:

-
doublezero status
-
-

Output:

-
-

Note

-

Examine this output. Notice that the Tunnel src, and the DoubleZero IP match the public ipv4 address on your machine. -

-
-

 Tunnel status | Last Session Update     | Tunnel Name | Tunnel src    | Tunnel dst     | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro     | Network
- up            | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL      | ams-dz001      |  ams-dz001          | Amsterdam | mainnet-beta
-
-A status of up means you are successfully connected.

-

You will be able to view routes propagated by other users on DoubleZero by running:

-
ip route
-
-
default via 149.28.38.1 dev enp1s0 proto dhcp src 149.28.38.64 metric 100
-5.39.216.186 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.201 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.202 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-...
-
-
-

Up Next: Publishing Shreds via Multicast

-

If you have completed this setup and plan publishing shreds via multicast, proceed to the next page.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/DZ Testnet Connection/index.html b/site/DZ Testnet Connection/index.html deleted file mode 100644 index 1ea3375..0000000 --- a/site/DZ Testnet Connection/index.html +++ /dev/null @@ -1,1448 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Validator Testnet Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Validator Testnet Connection in IBRL Mode

-
-By connecting to DoubleZero testnet I agree to the terms of the Evaluation Agreement set forth here (click to expand) -

DoubleZero Testnet -Evaluation Agreement

-

By accessing or using the Solution (defined below), you agree as of the -first date of such access (the "Effective Date") that this -Evaluation Agreement (the "Agreement") sets forth the terms and -conditions under which DoubleZero Foundation ("DZF") will provide -you ("User" or "you") access to the Solution on an evaluation -basis. In consideration of the mutual promises herein, you agree as -follows:

-

1. DEFINITIONS.

-

1.1 "Confidential Information" means any and all information disclosed by either party to the other which is designated as confidential, or which should otherwise be understood to be confidential, including but not limited to, the Solution, product plans, business plans, trade secrets, technology, or any other proprietary information.

-

1.2 "Solution" means the testnet version of the DoubleZero high-performance network infrastructure for web3 projects ("Testnet") and related edge filtering service with integrated bandwidth ("Information Service") the DZ Software (defined below), any and all materials provided by DZF relating to the DZ Software ("Documentation"), and other materials that DZF provides to User hereunder.

-

2. ACCESS.

-

2.1 Access to Solution. Subject to the terms and conditions of this Agreement, DZF will provide User access to the Solution through the Internet. User's access is a non-exclusive, non-transferable, limited use of the Solution to enable User to evaluate the Information Service only. With respect to any software comprising the Solution ("DZ Software"), DZF hereby grants User a limited, revocable license, during the Evaluation Period, to copy, download, make a reasonable number of copies of, run, and deploy (as applicable) such DZ Software solely as contemplated by the Documentation.

-

2.2 Restrictions. User may use the Solution in accordance with this Agreement from the Effective Date until terminated by DZF (the "Evaluation Period"). User understands that any rights to use the Solution beyond the Evaluation Period will be subject to a separate commercial agreement between the parties with respect thereto, including the payment of fees. User shall not, and shall not permit any third party to: (i) modify or create any derivative works based on the Solution or any portion thereof; (ii) reproduce the Solution except as expressly permitted by this Agreement; (iii) sublicense, distribute, sell, lend, rent, lease, transfer, or grant any rights in or to all or any portion of the Solution or provide access to the Solution to third parties, on a service bureau basis or otherwise, except as an offering of the Information Services through or in connection with User's platform or product and not on a standalone basis; or (iv) use the Solution other than as provided herein.

-

2.3 Ownership. DZF retains all right, title and interest, including intellectual property rights, in and to the Solution.

-

3 FEEDBACK. -DZF may periodically request that User provide, and User agrees to provide to DZF, feedback regarding the use, operation, and functionality of the Solution ("Feedback"). User hereby grants DZF a non-exclusive, worldwide, perpetual, irrevocable, royalty-free, fully paid-up, fully sublicensable and transferable right and license to use and incorporate Feedback into any products and services, to make, use, sell, offer for sale, import, and otherwise exploit such products and services, and to otherwise use, copy, distribute, and otherwise exploit the Feedback without restriction.

-

4. TERM AND TERMINATION.

-

4.1 Term. This Agreement will commence as of the Effective Date and will remain in full force and effect for the Evaluation Period. Either party may terminate this Agreement immediately for convenience, for any reason or no reason, upon written notice to the other party (email to suffice).

-

4.1 Effects of Termination. Upon termination of this Agreement for any reason: (i) the rights granted to User hereunder will immediately terminate; (ii) User shall immediately discontinue any use of the Solution and shall return or destroy all Documentation and any DZ Software under its control; (iii) each party shall promptly return or destroy all Confidential Information and property of the other party; and (iv) Sections 2.2, 2.3, 3, 4.2, and 5 through 8 will survive.

-

5. CONFIDENTIALITY. -Each party agrees that it will use the Confidential Information of the other party solely to perform its obligations and exercise its rights under this Agreement and it will not disclose, or permit to be disclosed, the same, except as otherwise permitted hereunder. However, either party may disclose Confidential Information to its personnel, attorneys, and other representatives who have a need to know and are bound by confidentiality obligations no less protective than those set forth in this Agreement; and as required by law (in which case the receiving party will provide the disclosing party with prior notice thereof and opportunity to contest such disclosure, and will minimize such disclosure to the extent permitted by applicable law). The obligations of confidentiality in this Section 5 shall not apply to information that: (a) is or becomes generally known or publicly available through no fault of the receiving party; (b) was properly known to the receiving party, without restriction, prior to disclosure by the disclosing party; (c) was properly disclosed to the receiving party, without restriction, by another person with the legal authority to do so; or (d) is independently developed by the receiving party without use of or reference to the disclosing party's Confidential Information. Each party agrees to exercise due care in protecting the Confidential Information of the other party from unauthorized use and disclosure. In the event of actual or threatened breach of the provisions of this Section or the licenses contained herein, the non-breaching party will be entitled to seek immediate injunctive and other equitable relief, without waiving any other rights or remedies available to it. User is responsible for maintaining the Solution and the secrecy of any passwords, seed phrases, or codes that provide access to the Solution as the Confidential Information of DZF. Nothing herein limits or restricts DZF's right or ability to use data regarding the performance, availability, usage, integrity and security of the Solution. If either party breaches, or threatens to breach the provisions of this Section 5, each party agrees that the non-breaching party will have no adequate remedy at law and is therefore entitled to immediate injunctive and other equitable relief, without bond and without the necessity of showing actual money damages.

-

6. WARRANTY DISCLAIMER; LIMITATION OF LIABILITY.

-

6.1 WARRANTY DISCLAIMER. THE SOLUTION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. DZF MAKES NO WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE WITH RESPECT TO THE SOLUTION AND DOCUMENTATION INCLUDING THEIR CONDITION, CONFORMITY TO ANY REPRESENTATION OR DESCRIPTION, AND DZF SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.

-

6.2 LIMITATION OF LIABILITY. -EXCEPT FOR A BREACH OF SECTIONS 2.1, 2.2, AND 5, IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER FOR INDIRECT, INCIDENTAL, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS OR USE OR LOSS OF DATA, INCURRED BY YOU OR ANY THIRD PARTY, ARISING OUT OF OR RELATED TO THIS AGREEMENT WHETHER IN AN ACTION IN CONTRACT, TORT, OR OTHERWISE, EVEN IF THE OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT SHALL DZF'S AGGREGATE LIABILITY ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED ONE HUNDRED DOLLARS ($100), WHETHER AN ACTION IN CONTRACT, TORT, OR OTHERWISE. THE FOREGOING LIMITATIONS WILL APPLY NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY HEREIN. THE PARTIES AGREE THAT THE FOREGOING LIMITATIONS REPRESENT A REASONABLE ALLOCATION OF RISK UNDER THIS AGREEMENT.

-

7. GOVERNING LAW. -This Agreement and all matters arising out of or relating to this Agreement shall be governed, interpreted and constructed in accordance with the laws of the Cayman Islands. Should a controversy, dispute or claim arise out of or in relation to this Agreement ("Dispute"), the relevant party as appropriate, must give 30 days' notice of such Dispute to the other parties (the "Notice of Dispute"). Should the Dispute not be resolved at the expiration of 30 days after service of the Notice of Dispute, the relevant party may commence arbitration proceedings as provided herein. Should the Dispute remain at the expiration of 30 days after service of the Notice of Dispute, the Dispute shall be settled by arbitration administered by the Cayman International Mediation & Arbitration Centre (CI-MAC) in accordance with the CI-MAC Arbitration Rules (the "Arbitration Rules") in force as at the date of this Agreement, which Arbitration Rules are deemed to be incorporated by reference to this clause, and governed by the Arbitration Act (as amended). The arbitration shall be seated in George Town, Grand Cayman, Cayman Islands and governed by Cayman Islands law. The language of the arbitration shall be English. The arbitration shall be determined by a sole arbitrator to be appointed in accordance with the Arbitration Rules. Any award or decision made by the arbitrator shall be in writing and shall be final and binding on the parties without any right of appeal, and judgment upon any award thus obtained may be entered in or enforced by any court having jurisdiction thereof. No action at law or in equity based upon any claim arising out of or related to this Agreement shall be instituted in any court of any jurisdiction. If any litigation or arbitration is necessary to enforce the terms of this Agreement, the prevailing party will be entitled to have their attorney fees paid by the other party. Each party waives any right it may have to assert the doctrine of forum non conveniens, to assert that it is not subject to the jurisdiction of such arbitration or courts or to object to venue to the extent any proceeding is brought in accordance herewith.

-

8. GENERAL PROVISIONS. -This Agreement may not be transferred or assigned by User without the prior written consent of DZF. DZF may freely assign this Agreement. All notices required to be sent hereunder shall be sent by email (to DZF: legal@doublezero.xyz) and deemed received the day after sending (with transmission confirmed). If any provision of this Agreement is held to be invalid or unenforceable, the remaining provisions of this Agreement will remain in full force and effect. The waiver by either party of any default or breach of this Agreement shall not constitute a waiver of any other or subsequent default or breach. Neither party shall be liable for any delay or failure in performance due to acts of God, earthquakes, shortages of supplies, transportation difficulties, labor disputes, riots, war, fire, epidemics, and similar occurrences beyond its control, whether or not foreseeable. This Agreement together with any attachments constitutes the complete agreement between the parties and supersedes all prior or contemporaneous agreements or representations, written or oral, concerning the subject matter herein. This Agreement may not be modified or amended except in writing signed by a duly authorized representative of each party.

-
-

Connecting to Testnet in IBRL Mode

-
-

Note

-

IBRL mode does not require restarting validator clients, because it uses your existing public IP address.

-
-

Solana Testnet Validators will complete connection to DoubleZero Testnet, which is detailed on this page.

-

Each Solana validator has its own identity keypair; from this, extract the public key known as the node ID. This is the validator's unique fingerprint on the Solana network.

-

With the DoubleZeroID and node ID identified, you will prove ownership of your machine. This is done by creating a message which includes the DoubleZeroID signed with the validator's identity key. The resulting cryptographic signature serves as verifiable proof that you control the validator.

-

Finally, you will submit a connection request to DoubleZero. This request communicates: "Here is my identity, here is proof of ownership, and here is how I intend to connect." DoubleZero validates this information, accepts the proof, and provisions network access for the validator on DoubleZero.

-

This guide allows for 1 Primary Validator to register itself, and up to 3 backup/failover machines at the same time.

-

Prerequisites

-
    -
  • Solana CLI installed and on $PATH
  • -
  • For validators: Permission to access to the validator identity keypair file (e.g., validator-keypair.json) under the sol user
  • -
  • For validators: Verify the Identity key of Solana validator being connected has at least 1 SOL on it
  • -
  • Firewall rules permit outbound connections for DoubleZero and Solana RPC as needed including - GRE (ip proto 47) and BGP (169.254.0.0/16 on tcp/179)
  • -
-
-

Info

-

The Validator ID will be checked against Solana gossip to determine the target IP. The target IP, and the DoubleZero ID will then be used when opening a GRE tunnel between your machine and the target DoubleZero Device.

-

Consider: In the case where you have a junk ID and Primary ID on at the same IP, only the Primary ID will be used in registration of the machine. This is because the junk ID will not appear in gossip, and therefore cannot be used to verify the IP of the target machine.

-
-

1. Environment Configuration

-

Please follow the setup instructions before proceeding.

-

The last step in setup was to disconnect from the network. This is to ensure that only one tunnel is open on your machine to DoubleZero, and that tunnel is on the correct network.

-
-

To configure the DoubleZero Client CLI (doublezero) and daemon (doublezerod) to connect to DoubleZero testnet: -

DESIRED_DOUBLEZERO_ENV=testnet \
-    && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \
-    && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \
-    && sudo systemctl daemon-reload \
-    && sudo systemctl restart doublezerod \
-    && doublezero config set --env $DESIRED_DOUBLEZERO_ENV  > /dev/null \
-    && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV"
-
-You should see the following output: -
✅ doublezerod configured for environment testnet
-

-

After about 30 seconds you will see the DoubleZero devices available:

-

doublezero latency
-
-Example output (testnet) -
doublezero latency
- pubkey                                       | code         | ip             | min      | max      | avg      | reachable
- 6E1fuqbDBG5ejhYEGKHNkWG5mSTczjy4R77XCKEdUtpb | nyc-dz001    | 64.86.249.22   | 2.44ms   | 2.63ms   | 2.50ms   | true
- CT8mP6RUoRcAB67HjKV9am7SBTCpxaJEwfQrSjVLdZfD | lax-dz001    | 207.45.216.134 | 71.97ms  | 72.01ms  | 71.99ms  | true
- Cpt3doj17dCF6bEhvc7VeAuZbXLD88a1EboTyE8uj6ZL | lon-dz001    | 195.219.120.66 | 71.94ms  | 72.08ms  | 72.00ms  | true
- 4Wr7PQr5kyqCNJo3RKa8675K7ZtQ6fBUeorcexgp49Zp | ams-dz001    | 195.219.138.50 | 76.55ms  | 76.65ms  | 76.61ms  | true
- 29ghthsKeH2ZCUmN2sUvhJtpEXn2ZxqAuq4sZFBFZmEs | fra-dz001    | 195.219.220.58 | 83.01ms  | 83.10ms  | 83.05ms  | true
- hWffRFpLrsZoF5r9qJS6AL2D9TEmSvPUBEbDrLc111Y  | fra-dz-001-x | 195.12.227.250 | 84.87ms  | 84.91ms  | 84.89ms  | true
- 8jyamHfu3rumSEJt9YhtYw3J4a7aKeiztdqux17irGSj | prg-dz-001-x | 195.12.228.250 | 95.27ms  | 95.30ms  | 95.29ms  | true
- 5tqXoiQtZmuL6CjhgAC6vA49JRUsgB9Gsqh4fNjEhftU | tyo-dz001    | 180.87.154.78  | 180.96ms | 181.08ms | 181.02ms | true
- D3ZjDiLzvrGi5NJGzmM7b3YZg6e2DrUcBCQznJr3KfC8 | sin-dz001    | 180.87.102.98  | 220.87ms | 221.14ms | 220.97ms | true
-

-
-

Mainnet output will be identical in structure, but with many more available devices. -

-

2. Open port 44880

-

Users need to open port 44880 to utilize some routing features.

-

To open port 44880 you could update IP tables such as:

-
-
sudo iptables -A INPUT -i doublezero0 -p udp --dport 44880 -j ACCEPT
-sudo iptables -A OUTPUT -o doublezero0 -p udp --dport 44880 -j ACCEPT
-
-
-

note the -i doublezero0, -o doublezero0 flags which restrict this rule to only the DoubleZero interface

-

Or UFW such as:

-
-
sudo ufw allow in on doublezero0 to any port 44880 proto udp
-sudo ufw allow out on doublezero0 to any port 44880 proto udp
-
-
-

note the in on doublezero0, out on doublezero0 flags which restrict this rule to only the DoubleZero interface

-

3. Attest Validator Ownership

-
-

With your DoubleZero Environment set, it is now time to attest to your Validator Ownership.

-

The DoubleZero ID you created in the setup of your primary validator must be used on all backup machines.

-

The ID on your primary machine can be found with doublezero address The same ID must be in ~/.config/doublezero/id.json on all machines in the cluster.

-

In order to accomplish this you will first verify the machine you are running the commands from is your Primary Validator with:

-
doublezero-solana passport find-validator -ut
-
-

This verifies that the validator is registered in gossip and appears in the leader schedule.

-

Expected output:

-
Connected to Solana: testnet
-
-DoubleZero ID: YourDoubleZeroAddress11111111111111111111111111111
-Detected public IP: 11.11.11.111
-Validator ID: ValidatorIdentity111111111111111111111111111
-Gossip IP: 11.11.11.111
-In Leader scheduler
-✅ This validator can connect as a primary in DoubleZero 🖥️  💎. It is a leader scheduled validator.
-
-
-

Info

-

The same workflow is used for one, or many machines. -To register one machine exclude the arguments "--backup-validator-ids" or "backup_ids=" from any commands on this page.

-
-

Now, on all backup machines you intend to run your Primary Validator on execute the following:

-
doublezero-solana passport find-validator -ut
-
-

Expected output:

-

Connected to Solana: testnet
-
-DoubleZero ID: YourDoubleZeroAddress11111111111111111111111111111
-Detected public IP: 22.22.22.222
-Validator ID: ValidatorIdentity222222222222222222222222222
-Gossip IP: 22.22.22.222
-In Not in Leader scheduler
- ✅ This validator can only connect as a backup in DoubleZero 🖥️  🛟. It is not leader scheduled and cannot act as a primary validator.
-
-This output is expected. The backup node cannot be in the leader schedule at time of pass creation.

-

You will now run this command on all backup machines you plan to use your Primary Validator vote account, and identity on.

-
-
-

Prepare the Connection

-

Run the following command on the Primary Validator machine. This is the machine you have active stake on, that is in the leader schedule with your primary validator ID in solana gossip on the machine you are running the command from:

-
doublezero-solana passport prepare-validator-access -ut \
-  --doublezero-address YourDoubleZeroAddress11111111111111111111111111111 \
-  --primary-validator-id ValidatorIdentity111111111111111111111111111 \
-  --backup-validator-ids ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444>
-
-

Example output:

-

DoubleZero Passport - Prepare Validator Access Request
-Connected to Solana: testnet
-
-Primary validator 🖥️  💎:
-  ID: ValidatorIdentity111111111111111111111111111
-  Gossip: ✅ OK 11.11.11.111)
-  Leader scheduler: ✅ OK (Stake: 1,050,000.00 SOL)
-
-Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity222222222222222222222222222
-  Gossip: ✅ OK (22.22.22.222)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-
-Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity333333333333333333333333333
-  Gossip: ✅ OK (33.33.33.333)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-
-  Backup validator 🖥️ 🛡️:
-  ID: ValidatorIdentity444444444444444444444444444
-  Gossip: ✅ OK (33.33.33.333)
-  Leader scheduler:  ✅ OK (not a leader scheduled validator)
-
-  To request access, sign the following message with your validator's identity key:
-
-  solana sign-offchain-message \
-     service_key=YourDoubleZeroAddress11111111111111111111111111111,backup_ids=ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
-     -k <identity-keypair-file.json>
-
-Note the output at the end of this command. It is the structure for the next step.

-
-

4. Generate Signature

-
-

At the end of the last step, we received a pre-formatted output for solana sign-offchain-message

-

From the above output we will run this command on the Primary Validator machine.

-
  solana sign-offchain-message \
-     service_key=YourDoubleZeroAddress11111111111111111111111111111,backup_ids=ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
-     -k <identity-keypair-file.json>
-
-

Output:

-
  Signature111111rrNykTByK2DgJET3U6MdjSa7xgFivS9AHyhdSG6AbYTeczUNJSjYPwBGqpmNGkoWk9NvS3W7
-
-
-

5. Initiate a Connection Request in DoubleZero

-
-

Use the request-validator-access command to create an account on Solana for the connection request. The DoubleZero Sentinel agent detects the new account, validates its identity and signature, and creates the access pass in DoubleZero so the server can establish a connection.

-

Use the node ID, DoubleZeroID, and signature.

-
-

Note

-

In this example we use -k /home/user/.config/solana/id.json to find the validator Identity. Use the appropriate location for your local deployment.

-
-
doublezero-solana passport request-validator-access -k <path to keypair> -ut \
---primary-validator-id ValidatorIdentity111111111111111111111111111 \
---backup-validator-ids ValidatorIdentity222222222222222222222222222,ValidatorIdentity33333333333333333333333333,ValidatorIdentity444444444444444444444444444 \
---signature Signature111111rrNykTByK2DgJET3U6MdjSa7xgFivS9AHyhdSG6AbYTeczUNJSjYPwBGqpmNGkoWk9NvS3W7 --doublezero-address YourDoubleZeroAddress11111111111111111111111111111
-
-

Output:

-

This output can be used to see the transaction on a Solana explorer. Be sure to change the explorer to testnet. This verification is optional.

-
Request Solana validator access: Transaction22222222VaB8FMqM2wEBXyV5THpKRXWrPtDQxmTjHJHiAWteVYTsc7Gjz4hdXxvYoZXGeHkrEayp
-
-

If successful, DoubleZero will register the primary with its backups. You may now failover between the IPs registered in the access pass. DoubleZero will maintain connectivity automatically when switching to backup nodes registered in this way.

-
-

6. Connect in IBRL Mode

-
-

On the server, with the user which will connect to DoubleZero, run the connect command to establish the connection to DoubleZero.

-
doublezero connect ibrl
-
-

You should see output indicating provisioning, such as:

-

DoubleZero Service Provisioning
-🔗  Start Provisioning User...
-Public IP detected: 137.184.101.183 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x`
-🔍  Provisioning User for IP: 137.184.101.183
-    User account created
-    Connected to device: nyc-dz001
-    The user has been successfully activated
-    Service provisioned with status: ok
-✅  User Provisioned
-
-Wait one minute for the GRE tunnel to finish setting up. Until the GRE tunnel is done setting up, your status output may return "down" or "Unknown"

-

Verify your connection:

-
doublezero status
-
-

Output:

-
-

Note

-

Examine this output. Notice that the Tunnel src, and the DoubleZero IP match the public ipv4 address on your machine. -

-
-

 Tunnel status | Last Session Update     | Tunnel Name | Tunnel src    | Tunnel dst     | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro     | Network
- up            | 2025-10-20 12:12:55 UTC | doublezero0 | 11.11.11.111 | 12.34.56.789 | 11.11.11.111 | IBRL      | ams-dz001      |  ams-dz001          | Amsterdam | testnet
-
-A status of up means you are successfully connected.

-

You will be able to view routes propagated by other users on DoubleZero by running:

-
ip route
-
-
default via 149.28.38.1 dev enp1s0 proto dhcp src 149.28.38.64 metric 100
-5.39.216.186 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.201 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.202 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-...
-
-
-

Up Next: Publishing Shreds via Multicast

-

If you have completed this setup and plan publishing shreds via multicast, proceed to the next page.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/New Tenant/index.html b/site/New Tenant/index.html deleted file mode 100644 index 01033a3..0000000 --- a/site/New Tenant/index.html +++ /dev/null @@ -1,1051 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - New Tenant - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

New Tenant

-

If you would like your distributed system to leverage the DoubleZero network, we’re happy to assist with any technical questions you may have.

-

Contact Us Form

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/Other Multicast Connection/index.html b/site/Other Multicast Connection/index.html deleted file mode 100644 index e29ba99..0000000 --- a/site/Other Multicast Connection/index.html +++ /dev/null @@ -1,1206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Other Multicast Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Other Multicast Connection

-
-

By connecting to DoubleZero I agree to the DoubleZero Terms of Service

-
- - - - - - - - - - - - - - - -
Use CaseFirst StepWhen Approve, connect via:
Subscribe to Jito ShredstreamContact Jito for approval.doublezero connect multicast --subscribe jito-shredstream
-

Detailed connection information:

-

1. DoubleZero Client Installation

-

Please follow the setup instructions to Install and configure the DoubleZero client.

-

2. Connection Instructions

-

Connect to DoubleZero in Multicast Mode -As a publisher:

-

doublezero connect multicast --publish <feed name>

-

or as a subscriber:

-

doublezero connect multicast --subscribe <feed name>

-

or to publish and subscribe:

-

doublezero connect multicast --publish <feed name> --subscribe <feed name>

-

To publish or subscribe to multiple feeds you can include multiple feed names space separated. -This can also be use to publish and subscribe to publish feeds. -For example -doublezero connect multicast --subscribe feed1 feed2 feed3

-

You should see output similar to the following: -

DoubleZero Service Provisioning
-🔗  Start Provisioning User to devnet...
-Public IP detected: 137.174.145.145 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x`
-    DoubleZero ID: <your dz_id>
-🔍  Provisioning User for IP: <your public ip>
-    Creating an account for the IP: <your public ip>
-    The Device has been selected: <the doublezero device you are connecting to>
-    Service provisioned with status: ok
-✅  User Provisioned
-

-

3. Verify your active multicast connection.

-

Wait for 60 seconds and then run

-

doublezero status
-
-Expected result: -- BGP Session Up on the correct DoubleZero Network -- If you are a publisher, your DoubleZero IP will be different than your Tunnel Src IP. This is expected. -- If you are a subscriber only, your DoubleZero IP will be the same as your Tunnel Src IP.

-
~$ doublezero status
- Tunnel Status  | Last Session Update     | Tunnel Name | Tunnel Src      | Tunnel Dst | Doublezero IP | User Type | Current Device | Lowest Latency Device | Metro   | Network
- BGP Session Up | 2026-02-11 20:46:20 UTC | doublezero1 | 137.174.145.145 | 100.0.0.1  | 198.18.0.1    | Multicast | ams-dz001      | ✅ ams-dz001         | Amsterdam | Testnet
-
-

Verify the groups you're connected to: -

doublezero user list --client-ip <your ip>
-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
accountuser_typegroupsdevicelocationcyoa_typeclient_ipdz_ipaccesspasstunnel_idtunnel_netstatusowner
wQWmt7L6mTyszhyLywJeTk85KJhe8BGW4oCcmxbhaxJMulticastP:mg02ams-dz001AmsterdamGREOverDIA137.174.145.145198.18.0.1Prepaid: (MAX)515169.254.3.58/31activatedDZfHfcCXTLwgZeCRKQ1FL1UuwAwFAZM93g86NMYpfYan
- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/Permissioned Connection/index.html b/site/Permissioned Connection/index.html deleted file mode 100644 index 9e9cd10..0000000 --- a/site/Permissioned Connection/index.html +++ /dev/null @@ -1,1245 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Permissioned Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Non-Validator Permissioned Connection to DoubleZero in IBRL Mode

-
-

By connecting to DoubleZero I agree to the DoubleZero Terms of Service

-
-
-

Permissioned User Onboarding Overview

-

User onboarding is currently permissioned for non-validators and RPCs. To begin the permissioned flow, please fill out this form. Here is what to expect during this process:

-
    -
  • There may be fees associated with Permissioned User usage in the future.
  • -
  • After form submission monitor your primary Telegram contact.
  • -
-
-

Connecting to Mainnet-Beta and Testnet in IBRL Mode

-
-

Note

-

IBRL mode does not require restarting validator clients, because it uses your existing public IP address.

-
-

Permissioned Users will complete connection to DoubleZero Mainnet-beta, which is detailed on this page.

-

1. Environment Configuration

-

Please follow the setup instructions before proceeding.

-

The last step in setup was to disconnect from the network. This is to ensure that only one tunnel is open on your machine to DoubleZero, and that tunnel is on the correct network.

-

To configure the DoubleZero Client CLI (doublezero) and daemon (doublezerod) to connect to DoubleZero testnet: -

DESIRED_DOUBLEZERO_ENV=testnet \
-    && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \
-    && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \
-    && sudo systemctl daemon-reload \
-    && sudo systemctl restart doublezerod \
-    && doublezero config set --env $DESIRED_DOUBLEZERO_ENV  > /dev/null \
-    && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV"
-
-To configure the DoubleZero Client CLI (doublezero) and daemon (doublezerod) to connect to DoubleZero mainnet-beta: -
DESIRED_DOUBLEZERO_ENV=mainnet-beta \
-    && sudo mkdir -p /etc/systemd/system/doublezerod.service.d \
-    && echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/doublezerod -sock-file /run/doublezerod/doublezerod.sock -env $DESIRED_DOUBLEZERO_ENV" | sudo tee /etc/systemd/system/doublezerod.service.d/override.conf > /dev/null \
-    && sudo systemctl daemon-reload \
-    && sudo systemctl restart doublezerod \
-    && doublezero config set --env $DESIRED_DOUBLEZERO_ENV  > /dev/null \
-    && echo "✅ doublezerod configured for environment $DESIRED_DOUBLEZERO_ENV"
-

-

You should see the following output: -

✅ doublezerod configured for environment mainnet-beta
-
-You should see the following output: -✅ doublezerod configured for environment testnet

-

After about 30 seconds you will see the DoubleZero devices available:

-

doublezero latency
-
-Example output (Testnet) -
 pubkey                                       | code          | ip              | min      | max      | avg      | reachable 
- 2hPMFJHh5BPX42ygBvuYYJfCv9q7g3rRR3ZRsUgtaqUi | dz-ny7-sw01   | 137.239.213.162 | 1.74ms   | 1.92ms   | 1.84ms   | true      
- ETdwWpdQ7fXDHH5ea8feMmWxnZZvSKi4xDvuEGcpEvq3 | dz-ny5-sw01   | 137.239.213.170 | 1.88ms   | 4.39ms   | 2.72ms   | true      
- 8J691gPwzy9FzUZQ4SmC6jJcY7By8kZXfbJwRfQ8ns31 | nyc002-dz002  | 38.122.35.137   | 2.45ms   | 3.30ms   | 2.74ms   | true      
- 8gisbwJnNhMNEWz587cAJMtSSFuWeNFtiufPuBTVqF2Z | dz-ny7-sw02   | 142.215.184.122 | 1.88ms   | 5.13ms   | 3.02ms   | true      
- uzyg9iYw2FEbtdTHaDb5HoeEWYAPRPQgvsgyd873qPS  | nyc001-dz002  | 4.42.212.122    | 3.17ms   | 3.63ms   | 3.33ms   | true      
- FEML4XsDPN3WfmyFAXzE2xzyYqSB9kFCRrMik8JqN6kT | nyc001-dz001  | 38.104.167.29   | 2.33ms   | 5.46ms   | 3.39ms   | true      
- 9oKLaL6Hwno5TyAFutTbbkNrzxm1fw9fhzkiUHgsxgGx | dz-dc10-sw01  | 137.239.200.186 | 6.84ms   | 7.01ms   | 6.91ms   | true      
- DESzDP8GkSTpQLkrUegLkt4S2ynGfZX5bTDzZf3sEE58 | was001-dz002  | 38.88.214.133   | 7.39ms   | 7.44ms   | 7.41ms   | true      
- HHNCpqB7CwHVLxAiB1S86ko6gJRzLCtw78K1tc7ZpT5P | was001-dz001  | 66.198.11.74    | 7.67ms   | 7.85ms   | 7.76ms   | true      
- 9LFtjDzohKvCBzSquQD4YtL3HwuvkKBDE7KSzb8ztV2b | dz-mtl11-sw01 | 134.195.161.10  | 9.88ms   | 10.01ms  | 9.95ms   | true      
- 9M7FfYYyjM4wGinKPofZRNmQFcCjCKRbXscGBUiXvXnG | dz-tor1-sw01  | 209.42.165.10   | 14.52ms  | 14.53ms  | 14.52ms  | true           
-
-Testnet output will be identical in structure, but with many more available devices. -

-

2. Contact the DoubleZero Foundation

-

The DoubleZero foundation. You will need to provide your DoubleZeroID, your Validator ID (node ID), and the public ipv4 address you will be connecting from.

-
-

3. Connect in IBRL Mode

-

On the server, with the user which will connect to DoubleZero, run the connect command to establish the connection to DoubleZero.

-
doublezero connect ibrl
-
-

You should see output indicating provisioning, such as:

-

DoubleZero Service Provisioning
-🔗  Start Provisioning User...
-Public IP detected: 137.184.101.183 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x`
-🔍  Provisioning User for IP: 137.184.101.183
-    User account created
-    Connected to device: nyc-dz001
-    The user has been successfully activated
-    Service provisioned with status: ok
-✅  User Provisioned
-
-Wait one minute for the tunnel to complete. Until the tunnel is completed, your status output may return "down" or "Unknown"

-

Verify your connection:

-
doublezero status
-
-

Output: -

Tunnel status | Last Session Update     | Tunnel Name | Tunnel src      | Tunnel dst   | DoubleZero IP   | User Type
-up            | 2025-09-10 12:16:03 UTC | doublezero0 | 137.184.101.183 | 64.86.249.22 | 137.184.101.183 | IBRL
-
-A status of up means you are successfully connected.

-

You will be able to view routes propagated by other users on DoubleZero by running:

-

ip route
-
-Output:

-
default via 149.28.38.1 dev enp1s0 proto dhcp src 149.28.38.64 metric 100 
-5.39.216.186 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64 
-5.39.251.201 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64 
-5.39.251.202 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64 
-...
-
-
-

Up Next: Multicast

-

If you have completed this setup and plan to use Multicast, proceed to the next page.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/Shelby Permissioned Connection/index.html b/site/Shelby Permissioned Connection/index.html deleted file mode 100644 index f80c958..0000000 --- a/site/Shelby Permissioned Connection/index.html +++ /dev/null @@ -1,1195 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Shelby Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Connection to DoubleZero in IBRL Mode for Shelby Testnet Users

-
-

By connecting to DoubleZero I agree to the DoubleZero Terms of Service

-
-
-

Get your DoubleZeroID

-

You will need to provide your DoubleZeroID, and the public ipv4 address on this form

-
    -
  • There may be fees associated with Permissioned User usage in the future.
  • -
  • After form submission monitor your primary Telegram contact.
  • -
  • At this moment Shelby is only able to connect to DoubleZero Testnet.
  • -
-
-

Connecting to Testnet in IBRL Mode

-

Shelby permissioned users will complete connection to DoubleZero Testnet, which is detailed on this page.

-

1. Environment Configuration

-

Please follow the setup instructions before proceeding.

-

The last step in setup was to disconnect from the network. This is to ensure that only one tunnel is open on your machine to DoubleZero, and that tunnel is on the correct network.

-

To configure the DoubleZero Client CLI (doublezero) to connect to the Shelby tenant on DoubleZero : -

doublezero config set --tenant shelby
-

-

Apply additional Firewall rules specific to Shelby:

-

iptables: -

sudo iptables -A INPUT -i doublezero0 -p tcp --dport 39431 -j ACCEPT
-sudo iptables -A INPUT -p tcp --dport 39431 -j DROP
-

-

UFW: -

sudo ufw allow in on doublezero0 to any port 39431 proto tcp
-sudo ufw deny in to any port 39431 proto tcp
-

-

2. Contact the DoubleZero Foundation

-

The DoubleZero foundation. You will need to provide your DoubleZeroID, and the public ipv4 address you will be connecting from.

-
-

3. Connect in IBRL Mode

-

On the server, with the user which will connect to DoubleZero, run the connect command to establish the connection to DoubleZero.

-
doublezero connect ibrl
-
-

You should see output indicating provisioning, such as:

-

DoubleZero Service Provisioning
-🔗  Start Provisioning User...
-Public IP detected: 137.184.101.183 - If you want to use a different IP, you can specify it with `--client-ip x.x.x.x`
-🔍  Provisioning User for IP: 137.184.101.183
-    User account created
-    Connected to device: nyc-dz001
-    The user has been successfully activated
-    Service provisioned with status: ok
-✅  User Provisioned
-
-Wait one minute for the tunnel to complete. Until the tunnel is completed, your status output may return "down" or "Unknown"

-

Verify your connection:

-
doublezero status
-
-

Output: -

Tunnel status | Last Session Update     | Tunnel Name | Tunnel src      | Tunnel dst   | DoubleZero IP   | User Type
-up            | 2025-09-10 12:16:03 UTC | doublezero0 | 137.184.101.183 | 64.86.249.22 | 137.184.101.183 | IBRL
-
-A status of up means you are successfully connected.

-

You will be able to view routes propagated by other users on DoubleZero by running:

-

ip route
-
-Output:

-
default via 149.28.38.1 dev enp1s0 proto dhcp src 149.28.38.64 metric 100
-5.39.216.186 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.201 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-5.39.251.202 via 169.254.0.68 dev doublezero0 proto bgp src 149.28.38.64
-...
-
-
- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/Swapping-sol-to-2z/index.html b/site/Swapping-sol-to-2z/index.html deleted file mode 100644 index 92c8c96..0000000 --- a/site/Swapping-sol-to-2z/index.html +++ /dev/null @@ -1,1168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Swapping sol to 2z - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Swapping sol to 2z

- -

Review the Disclaimer before accessing or using the code or any related materials.

- - -
-Disclaimer -

This document and the associated code are provided for informational and technical purposes only. The token conversion functionality described herein is non-custodial—users interact directly with the underlying smart contracts and retain full control of their assets at all times.

-

The system may rely on or interact with third-party code, data sources, or pricing and fee mechanisms (for example, smart contracts, APIs, or decentralized exchanges) that are not developed, controlled, or reviewed by the developer(s) or publisher(s). No representation or warranty is made as to the accuracy, functionality, or security of any third-party component. -The developer(s) and publisher(s) of this code do not guarantee its accuracy, completeness, or continued availability. The code and related materials are provided “as is”, and may contain bugs, errors, or vulnerabilities. Use is entirely at your own risk. -The developer(s) and publisher(s) do not receive any fees in connection with the use of these contracts. They are under no obligation to maintain, update, or support the code or related documentation.

-

This document does not constitute an offer to sell, a solicitation to buy, or a recommendation to participate in any token conversion, swap or other transaction. No legal, financial, or investment advice is provided. -Users are solely responsible for determining the legality of their activities. They should review the laws and regulations applicable in their jurisdiction and consult independent advisors before using the code or participating in any conversion. Use is prohibited where it would be unlawful, including by persons or entities subject to sanctions or in restricted jurisdictions.

-

To the maximum extent permitted by law, the developer(s) and publisher(s) disclaim all liability for any loss, damage, or claim arising from or in connection with use of the code or participation in the conversion.

-

Review and use of this document and the associated code are subject to the Website Terms and Conditions and Protocol Terms and Conditions.

-
-

The DoubleZero protocol collects SOL-denominated revenue from its validator users but distributes 2Z-denominated rewards to contributors. Thus, it must convert SOL into 2Z.

-

To do so, eligible participants can trade against a DoubleZero swap contract, purchasing SOL from the contract and selling it 2Z. Pricing is based on Pyth price feeds with a programmatic discount mechanism.

-

This short guide explains how to use the program.

-

Review the Disclaimer at the end of this document before accessing or using the code or any related materials.

-
-

Program Design

-

The swap program is effectively a one-sided liquidity pool that sells SOL in a fixed batch size of 1 SOL per trade. Any eligible participant can withdraw SOL from the program by depositing 2Z, at a price that is determined by an oracle price from Pyth and a dynamic discount. Over time, this executes the program’s goal of turning native tokens into 2Z.

-

To utilize, a trader must provide two recent Pyth prices (SOL/USD and 2Z/USD) and a quantity of 2Z. The program then calculates the 2Z needed to purchase that 1 SOL based on the implied SOL/2Z price. It then takes a few additional steps:

-
    -
  • It checks that the Pyth prices are sufficiently fresh, i.e. they are no more than 5 seconds stale.
  • -
  • It checks that the confidence intervals of the two prices are sufficiently small. That is, the sum of two Laplacing standard deviations (i.e. parameter conf in the Pyth price) for the two prices, normalized by their levels, must be less than or equal to 30 basis points.
  • -
  • It adjusts the SOL/2Z price by a dynamic discount, expressed as a percent of the Pyth price. This discount is a function of the time since the last trade. The formula below specifies the discount, assuming the last trade was made at slot \(s_{\text{last}}\) and the current slot is \(s_{\text{now}}\). (For instance, if 200 slots have elapsed since the last trade, the discount is 40 basis points.)
  • -
-
\[ -\text{discount} = \min\{0.00002 \times \left(s_{\text{now}} - s_{\text{last}}\right), 0.01\} -\]
-

At this point, if the trader has provided enough 2Z to execute the transaction at this computed price (inclusive of the discount), it executes at this computed price. It returns to the trader the purchased quantity of SOL and any excess 2Z.

-

The contract then permits no more trades for that slot. This is to prevent the contract from paying excessively high slippage should the Pyth price be far from the true price at any given point in time in ways that the existing filters do not catch issues.

-
-

Gasless Atomic Execution

-

This section will detail how to use the command harvest-dz. This command will atomically perform 2 actions. -1. The command requests a quote from Jupiter vs the native SOL <> 2Z conversion program. -2. When the Jupiter route yields more 2Z per SOL than the native conversion program requires, harvest-2z executes a swap, returning to your wallet 1 SOL plus the difference in 2Z.

-

Harvest 2Z

-

To execute, run the following: -

doublezero-solana revenue-distribution harvest-2z
-
-Output will resemble: -
Harvested 5.98151278 2Z tokens with 1.000000000 SOL
-
-The command can also be simulated with the --dry-run argument. Dry-run will produce program logs and an output resembling:

-
Simulated harvesting 5.98151278 2Z tokens with 1.000000000 SOL
-
-
-

Protocol Conversion

-

This section discusses checking conversion rates and executing the conversion using the doublezero-solana CLI. And at the end, we discuss the interface for custom-built integrations with the DoubleZero swap contract.

-

How to check the SOL/2Z conversion price via doublezero-solana

-

To find the SOL/2Z conversion rates on mainnet-beta, run the following command:

-
doublezero-solana revenue-distribution fetch sol-conversion
-
-

And the output you would see will resemble:

-
| field           | description                  | value         | note                          |
-|-----------------|------------------------------|---------------|-------------------------------|
-| Swap Rate       | 2Z amount for 1 SOL          | 805.72612992  |                               |
-| Swap Rate       | 2Z amount for 1 SOL          | 805.38772494  | Includes 0.04200000% discount |
-| Journal Balance | SOL available for conversion | 438.670881289 |                               |
-
-

The Journal Balance informs the user how much SOL liquidity there is in the Revenue Distribution smart contract. A user can trade as long as the Journal Balance exceeds the fixed trade size of 1 SOL.

-

The first row displays the "true" SOL/2Z conversion price via an offchain oracle. The second row is the conversion price used on-chain for the swap, which simply adjusts the true price for the algorithmic discount.

-

How to convert your 2Z to SOL via doublezero-solana

-

To convert your 2Z tokens to SOL, run the following command:

-
doublezero-solana revenue-distribution convert-2z
-
-

By default, if there is enough SOL liquidity and your ATA has enough 2Z to perform the swap, this transaction will succeed. You can more finely-tune the swap by specifying the following arguments:

-
      --limit-price <DECIMAL>                    Limit price defaults to the current SOL/2Z oracle price
-      --source-2z-account <PUBKEY>               Token account must be owned by the signer. Defaults to signer ATA if not specified
-      --checked-sol-amount <SOL>                 Explicitly check SOL amount. When specified, this amount will be checked against the fixed fill quantity
-
-

The specified limit price determines the worst-case price you are willing to accept when performing the SOL/2Z conversion. For example, let us say the discounted 2Z price for SOL is 800, which means 800 2Z tokens for 1 SOL. If you specify a limit price of 790, you are not willing to perform the swap because you are requiring that you only swap at most 790 2Z tokens for 1 SOL. But if you specify 810, the trade will go through because you were willing to swap at most 810 2Z tokens (and in this case, you will have only swapped 800 2Z tokens in this transaction).

-

The source 2Z token account overrides the default ATA using the signer as the owner of this 2Z ATA. But if you have another token account you want to use to perform the swap, provide the pubkey for it with this argument.

-

Optionally, you may specify the checked SOL amount to the standard fill size (set to 1 SOL at launch). If it does not align with the program’s fill size, the swap fails. This mitigates the risk that the program’s fill size changes and you do not notice.

-

Interface to Buy SOL

-

The interface and doublezero-solana CLI live in this repo. The source code for the DoubleZero swap contract interface can be found here. The program ID is 9DRcqsJUCo8CL2xDCXpogwzLEVKRDzSyNtVgXqsXHfDs.

-

A convenient way of generating the accounts needed for the buy SOL instruction is using the new method (found in instruction/account.rs).

-
pub fn new(
-    fill_registry_key: &Pubkey,
-    user_token_account_key: &Pubkey,
-    dz_mint_key: &Pubkey,
-    user_key: &Pubkey,
-) -> Self;
-
-

The fill_registry_key can be fetched either from the ProgramState

-
pub struct ProgramState {
-    pub admin_key: Pubkey,
-    pub fills_registry_key: Pubkey, // this key
-    pub is_paused: bool,
-    pub configuration_registry_bump: u8,
-    pub program_state_bump: u8,
-    pub deny_list_registry_bump: u8,
-    pub withdraw_authority_bump: u8,
-    pub last_trade_slot: u64,
-    pub deny_list_authority: Pubkey,
-}
-
-

Alternatively, you can call getProgramAccounts via Solana RPC with its discriminator. But we recommend caching this pubkey since it will never change.

-

The user_key is a signer for the buy SOL instruction and must be the owner of the user_token_account_key. As described above, this does NOT need to be an ATA. As long as your 2Z token account is owned by the user_key, this instruction will succeed.

-

The BuySolAccounts struct implements Into<Vec<AccountMeta>> so you can generate all of the account metas you need in order to build the instruction.

-

Instruction data is

-
    SolConversionInstructionData::BuySol {
-        limit_price: u64,
-        oracle_price_data: OraclePriceData,
-    },
-
-

This instruction data is Borsh-serialized and has an 8-byte Anchor selector, which will all serialize when using BorshSerialize::serialize.

-

The oracle price data can be fetched from this public endpoint: https://sol-2z-oracle-api-v1.mainnet-beta.doublezero.xyz/swap-rate. The data is serde-deserializable using the OraclePriceData struct found in oracle.rs.

-
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone, Default, PartialEq, Eq)]
-#[cfg_attr(
-    feature = "serde",
-    derive(serde::Deserialize),
-    serde(rename_all = "camelCase")
-)]
-pub struct OraclePriceData {
-    pub swap_rate: u64,
-    pub timestamp: i64,
-    pub signature: String,
-}
-
-

Example of how to fetch using the reqwest crate:

-
use anyhow::{Context, Result};
-
-pub async fn try_request_oracle_conversion_price(oracle_endpoint: &str) -> Result<OraclePriceData> {
-    reqwest::Client::new()
-        .get(oracle_endpoint)
-        .header("User-Agent", "SOL buyoooooooor")
-        .send()
-        .await?
-        .json()
-        .await
-        .with_context(|| format!("Failed to request SOL/2Z price from {oracle_endpoint}"))
-}
-
-

With the program ID, accounts and instruction data, you should be able to build the instruction to buy SOL from the DoubleZero swap contract.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/Validator Multicast Connection/index.html b/site/Validator Multicast Connection/index.html deleted file mode 100644 index 633c701..0000000 --- a/site/Validator Multicast Connection/index.html +++ /dev/null @@ -1,1233 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Validator Multicast Connection - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Validator Multicast Connection

-
-

By connecting to DoubleZero I agree to the DoubleZero Terms of Service

-
-
-

Trading firms and businesses

-

If you operate a trading firm or business looking to subscribe to the feed, more details will be shared soon. Please register interest to get more information here.

-
-

If you are not already connected to DoubleZero please complete Setup, and Mainnet-Beta validator connection documentation.

-

If you are a validator who is already connected to DoubleZero you may continue this guide.

-

1. Client Configuration

-

Jito-Agave (v3.1.9+)

-
    -
  1. -

    In your validator start script, add: --shred-receiver-address 233.84.178.1:7733

    -

    You are able to send to Jito and the bebop group at the same time.

    -

    example:

    -
    #!/bin/bash
    -export PATH="/home/sol/.local/share/solana/install/releases/v3.1.9-jito/bin:$PATH"
    -BLOCK_ENGINE_URL=https://ny.mainnet.block-engine.jito.wtf
    -RELAYER_URL=http://ny.mainnet.relayer.jito.wtf:8100
    -SHRED_RECEIVER_ADDR=<JitoBlockEngineAddress>
    -<...The rest of your config...>
    ---shred-receiver-address 233.84.178.1:7733
    -
    -
  2. -
  3. -

    Restart your validator.

    -
  4. -
  5. Connect to the DoubleZero multicast group bebop as a publisher: doublezero connect ibrl && doublezero connect multicast --publish bebop
  6. -
-

Frankendancer

-
    -
  1. -

    In config.toml, add:

    -
    [tiles.shred]
    -additional_shred_destinations_leader = [ "233.84.178.1:7733", ]
    -
    -
  2. -
  3. -

    Restart your validator.

    -
  4. -
  5. Connect to the DoubleZero multicast group bebop as a publisher: doublezero connect ibrl && doublezero connect multicast --publish bebop
  6. -
-

2. Confirm you're publishing leader shreds

-

Once you are connected you may check this dashboard to confirm you are publishing shreds. You will not see confirmation until after you have published leader shreds for at least one slot.

-

3. Validator Rewards

-

For each epoch where validators publish leader shreds, they will be proportionately rewarded for their contribution based on subscriptions. The specifics of this system will be announced, and detailed at a later date.

-

Troubleshooting

-

Retransmitting:

-
    -
  1. -

    A common cause of shred retransmission is a simple config. You may have the flag enabled to send retransmit shreds in your startup script; you will need to disable it.

    -

    The flag to remove in Jito-Agave is: --shred-retransmit-receiver-address.

    -
  2. -
  3. -

    Check the publisher dashboard and see if you have any retransmitted shreds. In the table, look at the No Retransmit Shreds column—a red X means you are retransmitting.

    -
    -

    epoch view

    -

    Note that there are different time windows to view the publisher dashboard. If you see retransmit in the 2 epoch view, but have made a recent change, try switching to the recent slot view.

    -
    -

    Publisher check dashboard

    -
  4. -
  5. -

    Find your client IP and look up your user in DoubleZero Data.

    -

    DoubleZero Data users

    -
  6. -
  7. -

    Click on Multicast to open your multicast view.

    -

    The screenshot below shows: Retransmitting (undesirable) steady outbound traffic with no leader-slot pattern.

    -

    User multicast view — retransmit example

    -

    The screenshot below shows: Healthy (publishing only leader shreds) outbound traffic in spikes, known as a sawtooth pattern, which line up with your leader slots.

    -

    User multicast view — healthy publisher example

    -
  8. -
-

The chart shows whether you are sending only leader shreds. Traffic spikes should line up with when you have a leader slot. When you have no leader slot there should be no traffic. If you are retransmitting, you will see a steady flow of traffic instead of slot-aligned spikes.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/architecture/index.html b/site/architecture/index.html deleted file mode 100644 index 7a1cb05..0000000 --- a/site/architecture/index.html +++ /dev/null @@ -1,1211 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Architecture - Malbec Labs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - -
-
- - - - - -

Architecture

-

What makes up the different actors and components of the DoubleZero network?

-
- Image title -
-
Figure 1: Network architecture components
-
-

Contributors

-

The DoubleZero network is made up of connectivity and packet processing contributions from a growing community of distributed network infrastructure providers in cities across the globe. Contributors bring fiber optic cable links and information processing resources to the protocol to provide the decentralized mesh network.

-

Network Bandwidth Contributors

-

Network contributors must provide dedicated bandwidth between two points, operate DoubleZero compatible devices (DZDs) at each end, and a connection to the internet at each end. Network contributors must also run DoubleZero software on each DZD to provide services like multicast, user lookup, and edge filtration services.

-

The physical links of the DoubleZero network are provided in the form of fiber optic cables, commonly referred to as wavelength services. Network contributors commit underutilized network links, owned or leased from infrastructure providers, between two or more data centers. These links are terminated at both ends by DoubleZero Devices, which are physical network switching enclosures running instances of the DoubleZero Agent software.

-

DoubleZero Exchange (DZX / Cross-connect Site)

-

DoubleZero Exchanges (DZXs) are interconnect points in the mesh network where different contributor links are bridged together. DZXs are located in major metropolitan areas around the world where network intersections occur. Network contributors must cross-connect their links into the broader DoubleZero mesh network at the DZXs geographically located nearest their link endpoints.

-

Computational Resource Contributors

-

Separate from network contributors, resource contributors are a decentralized group of network participants that perform various maintenance and monitoring duties necessary to sustain the technical integrity and ongoing functionality of the DoubleZero network. Specifically, they (i) track user transactions and payments; (ii) calculate fees for network contributors; (iii) record the results of (i) and (ii); (iv) administer, strictly on a non-discretionary basis, the smart contracts that control protocol tokenomics; (v) relay attestations to the applicable blockchain; and (vi) publish telemetry data regarding link quality and utilization to provide transparent, real-time performance metrics for all network contributors.

-

Components

-

DoubleZero Daemon

-

The DoubleZero Daemon software runs on servers needing to communicate over the DoubleZero network. The daemon interfaces with the host's kernel networking stack to create and manage tunnel interfaces, routing tables, and routes.

-

Activator

-

The Activator service, hosted by one or more computational resource contributing members of the DoubleZero community, monitors contract events that require IP address allocations and state changes and manages those changes on behalf of the network.

-

Controller

-

The Controller service, hosted by one or more computational resource contributors of the DoubleZero community, serves as the configuration interface for DoubleZero Device Agents to render their current configuration based on smart contract events.

-

Agent

-

The Agent software runs directly on DoubleZero Devices and applies configuration changes to the devices as interpreted by the Controller service. The Agent software polls the Controller for configuration changes, computes any differences between the canonical on-chain version of the Device state and the active configuration on the device and applies the necessary changes to reconcile the active configuration.

-

Device

-

The physical device enclosure providing the routing and link termination for the DoubleZero network. DZDs run the DoubleZero Agent software and are configured based on data read from the Controller service.

- - - - - - - - - - - - - -
-
- - - -
- -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/assets/images/favicon.png b/site/assets/images/favicon.png deleted file mode 100644 index 1cf13b9..0000000 Binary files a/site/assets/images/favicon.png and /dev/null differ diff --git a/site/assets/javascripts/bundle.f55a23d4.min.js b/site/assets/javascripts/bundle.f55a23d4.min.js deleted file mode 100644 index 01a46ad..0000000 --- a/site/assets/javascripts/bundle.f55a23d4.min.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Vi=Object.getOwnPropertyDescriptor;var Di=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,zi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Ni=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Di(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Vi(t,n))||o.enumerable});return e};var Lt=(e,t,r)=>(r=e!=null?Wi(zi(e)):{},Ni(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,(function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function ee(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,ee())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var qr=xr((dy,On)=>{"use strict";/*! - * escape-html - * Copyright(c) 2012-2013 TJ Holowaychuk - * Copyright(c) 2015 Andreas Lubbe - * Copyright(c) 2015 Tiancheng "Timothy" Gu - * MIT Licensed - */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */(function(t,r){typeof Rt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Rt=="object"?Rt.ClipboardJS=r():t.ClipboardJS=r()})(Rt,function(){return(function(){var e={686:(function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(D){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=D,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var V=f()(F);return u("copy"),F.remove(),V},ee=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=ee;function k(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(D)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,V=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:V});if(Y)return F==="cut"?y(Y):J(Y,{container:V})},qe=ft;function Fe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(D)}function ki(D,A){if(!(D instanceof A))throw new TypeError("Cannot call a class as a function")}function no(D,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=Fe(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var Y=this;this.listener=c()(V,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(V){var Y=V.delegateTarget||V.currentTarget,$e=this.action(Y)||"copy",Wt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Wt?"success":"error",{action:$e,text:Wt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return vr("action",V)}},{key:"defaultTarget",value:function(V){var Y=vr("target",V);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(V){return vr("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(V,Y)}},{key:"cut",value:function(V){return y(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof V=="string"?[V]:V,$e=!!document.queryCommandSupported;return Y.forEach(function(Wt){$e=$e&&!!document.queryCommandSupported(Wt)}),$e}}]),M})(s()),Ui=Fi}),828:(function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a}),438:(function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p}),879:(function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p}),817:(function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n}),279:(function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function z(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: -`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` - `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],z(i)),z(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Tr=Ue.EMPTY;function Nt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t})(j);var To=(function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t})(g);var _r=(function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(g);var _t={now:function(){return(_t.delegate||Date).now()},delegate:void 0};var At=(function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=_t);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t})(gt);var Lo=(function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(yt);var kr=new Lo(Oo);var Mo=(function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&o===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t})(gt);var _o=(function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t})(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Kt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Kt(Hr(e))?e.pop():void 0}function Yt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Bt(e){return H(e==null?void 0:e.then)}function Gt(e){return H(e[bt])}function Jt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Xt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Zt=Zi();function er(e){return H(e==null?void 0:e[Zt])}function tr(e){return fo(this,arguments,function(){var r,o,n,i;return Dt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rr(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Gt(e))return ea(e);if(xt(e))return ta(e);if(Bt(e))return ra(e);if(Jt(e))return Ao(e);if(er(e))return oa(e);if(rr(e))return na(e)}throw Xt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?Ve(t):Qo(function(){return new nr}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},ee=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;ee(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(ee,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(ee,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function Ht(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?kt(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function wt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?wt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function Tt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function De(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>De(e)),Q(De(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function ze(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return ze(e).pipe(m(({y:r})=>{let o=ce(e),n=Tt(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function Ne(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function $t(e){let t=matchMedia(e);return ir(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function zr(e,t){return e.pipe(v(r=>r?t():S))}function Nr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return Nr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return N([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(te("size")),n=N([o,r]).pipe(m(()=>De(e)));return N([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),St=JSON.parse(Ca.textContent);St.base=`${new URL(St.base,ye())}`;function xe(){return St}function B(e){return St.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?St.translations[e].replace("#",t.toString()):St.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Pt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Lt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=N([et(e),Ht(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(ze),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>N([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(kt(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());N([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>Ht(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return N([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),N([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>N([tn(e),ze(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Va(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Va(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Lt(Br());var Da=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function za(e){return ge(e).pipe(m(({width:t})=>({scrollable:Tt(e).width>t})),te("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Da++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),za(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function Na(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Na(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?wt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Vn=x("table");function Dn(e){return e.replaceWith(Vn),Vn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function zn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));N([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=De(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),N([ze(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=Tt(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.offsetWidth&&c.autoplay?c.play().catch(()=>{}):c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function Nn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Dn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>zn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?ze(o):I({x:0,y:0}),i=O(et(t),Ht(t)).pipe(K());return N([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=De(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Pt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=Ne("search");return N([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>N([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(te("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:n>0&&o>=n}}),te("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),te("bottom"))));return N([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=$t("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Lt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(te("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),te("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(te("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Lt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function It(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),Ne("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),N([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let p=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(p)||(p=[p]);e:for(let c of p)for(let l of n.aliases.concat(n.version))if(new RegExp(c,"i").test(l)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let p of ae("outdated"))p.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),Ne("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(It)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return N([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));N([t.pipe(Ae(It)),r],(i,a)=>a).pipe(te("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(te("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);Ne("search").subscribe(l=>{s.setAttribute("role",l?"list":"presentation"),s.hidden=!l}),o.pipe(re(r),Wr(t.pipe(Ae(It)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Dr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return N([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return N([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=De(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),Ve({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),Ve({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),Ve({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),te("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(te("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(te("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),te("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return N([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),te("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Vr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){N([Ne("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?wt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ft=sn(),Ot=ln(Ft),to=an(),Oe=gn(),hr=$t("(min-width: 60em)"),Mi=$t("(min-width: 76.25em)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ft,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ft,Ot).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),jt=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Ot})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>Nn(e,{viewport$:Oe,target$:Ot,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ft}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:jt})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?zr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt})):zr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ft;window.target$=Ot;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); -//# sourceMappingURL=bundle.f55a23d4.min.js.map - diff --git a/site/assets/javascripts/bundle.f55a23d4.min.js.map b/site/assets/javascripts/bundle.f55a23d4.min.js.map deleted file mode 100644 index e3de73f..0000000 --- a/site/assets/javascripts/bundle.f55a23d4.min.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], - "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 60em)\")\nconst screen$ = watchMedia(\"(min-width: 76.25em)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an